use crate::common::create_test_backend;
use tower_lsp::lsp_types::Position;
use std::sync::Arc;
#[test]
fn uri_format_consistency_simple_path() {
use tower_lsp::lsp_types::Url;
let path = std::path::Path::new("/home/user/project/src/Foo.php");
let from_format = format!("file://{}", path.display());
let from_url = Url::from_file_path(path).unwrap().to_string();
eprintln!("format!: {}", from_format);
eprintln!("Url: {}", from_url);
assert_eq!(
from_format, from_url,
"URI format mismatch for simple path — this would cause double entries in Find References"
);
}
#[test]
fn uri_format_consistency_path_with_spaces() {
use tower_lsp::lsp_types::Url;
let path = std::path::Path::new("/home/user/My Project/src/Foo.php");
let from_format = format!("file://{}", path.display());
let from_url = Url::from_file_path(path).unwrap().to_string();
eprintln!("format! (spaces): {}", from_format);
eprintln!("Url (spaces): {}", from_url);
if from_format != from_url {
eprintln!(
"WARNING: URI mismatch for path with spaces!\n format!: {}\n Url: {}",
from_format, from_url
);
}
assert!(
from_url.contains("My%20Project"),
"Url should percent-encode spaces: {}",
from_url
);
assert!(
from_format.contains("My Project"),
"format! should leave spaces as-is: {}",
from_format
);
}
#[test]
fn uri_format_consistency_path_with_special_chars() {
use tower_lsp::lsp_types::Url;
let path = std::path::Path::new("/home/user/project[1]/src/Foo.php");
let from_format = format!("file://{}", path.display());
let from_url = Url::from_file_path(path).unwrap().to_string();
eprintln!("format! (brackets): {}", from_format);
eprintln!("Url (brackets): {}", from_url);
if from_format != from_url {
eprintln!(
"WARNING: URI mismatch for path with brackets!\n format!: {}\n Url: {}",
from_format, from_url
);
}
}
#[test]
fn uri_format_consistency_path_with_hash() {
use tower_lsp::lsp_types::Url;
let path = std::path::Path::new("/home/user/project#2/src/Foo.php");
let from_format = format!("file://{}", path.display());
let from_url = Url::from_file_path(path).unwrap().to_string();
eprintln!("format! (hash): {}", from_format);
eprintln!("Url (hash): {}", from_url);
if from_format != from_url {
eprintln!(
"WARNING: URI mismatch for path with hash!\n format!: {}\n Url: {}",
from_format, from_url
);
}
}
fn open_file(backend: &phpantom_lsp::Backend, uri: &str, content: &str) {
backend
.open_files()
.write()
.insert(uri.to_string(), Arc::new(content.to_string()));
backend.update_ast(uri, content);
}
fn assert_no_duplicates(results: &[tower_lsp::lsp_types::Location], label: &str) {
let mut seen = std::collections::HashSet::new();
for loc in results {
let key = format!(
"{}:{}:{}:{}:{}",
loc.uri,
loc.range.start.line,
loc.range.start.character,
loc.range.end.line,
loc.range.end.character,
);
assert!(
seen.insert(key.clone()),
"Duplicate reference found ({}): {}",
label,
key
);
}
}
#[test]
fn class_references_no_duplicates() {
let backend = create_test_backend();
let uri = "file:///tmp/test_refs_class.php";
let content = r#"<?php
class Foo {
public function bar(): void {}
}
class Baz {
public function test(): void {
$f = new Foo();
$g = new Foo();
}
}
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(2, 6), true)
.expect("should find references");
assert_no_duplicates(&results, "class_references");
assert_eq!(
results.len(),
3,
"Expected 3 references (1 declaration + 2 usages), got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn class_references_without_declaration_no_duplicates() {
let backend = create_test_backend();
let uri = "file:///tmp/test_refs_class_nodecl.php";
let content = r#"<?php
class Foo {
public function bar(): void {}
}
class Baz {
public function test(): void {
$f = new Foo();
$g = new Foo();
}
}
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(2, 6), false)
.expect("should find references");
assert_no_duplicates(&results, "class_references_nodecl");
assert_eq!(
results.len(),
2,
"Expected 2 references (usages only), got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn method_references_no_duplicates() {
let backend = create_test_backend();
let uri = "file:///tmp/test_refs_method.php";
let content = r#"<?php
class Foo {
public function bar(): void {}
}
class Baz {
public function test(): void {
$f = new Foo();
$f->bar();
$f->bar();
}
}
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(3, 21), true)
.expect("should find references");
assert_no_duplicates(&results, "method_references");
assert_eq!(
results.len(),
3,
"Expected 3 references (1 declaration + 2 calls), got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn method_references_without_declaration_no_duplicates() {
let backend = create_test_backend();
let uri = "file:///tmp/test_refs_method_nodecl.php";
let content = r#"<?php
class Foo {
public function bar(): void {}
}
class Baz {
public function test(): void {
$f = new Foo();
$f->bar();
$f->bar();
}
}
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(3, 21), false)
.expect("should find references");
assert_no_duplicates(&results, "method_references_nodecl");
assert_eq!(
results.len(),
2,
"Expected 2 references (calls only), got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn property_references_no_duplicates() {
let backend = create_test_backend();
let uri = "file:///tmp/test_refs_prop.php";
let content = r#"<?php
class Foo {
public string $name = '';
public function test(): void {
$this->name = 'hello';
echo $this->name;
}
}
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(6, 16), true)
.expect("should find references");
assert_no_duplicates(&results, "property_references");
assert_eq!(
results.len(),
3,
"Expected 3 references (1 declaration + 2 accesses), got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn variable_references_no_duplicates() {
let backend = create_test_backend();
let uri = "file:///tmp/test_refs_var.php";
let content = r#"<?php
function test(): void {
$foo = 1;
$bar = $foo + 2;
echo $foo;
}
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(3, 5), true)
.expect("should find references");
assert_no_duplicates(&results, "variable_references");
assert_eq!(
results.len(),
3,
"Expected 3 references (1 definition + 2 usages), got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn static_method_references_no_duplicates() {
let backend = create_test_backend();
let uri = "file:///tmp/test_refs_static.php";
let content = r#"<?php
class Foo {
public static function create(): self {
return new self();
}
}
class Baz {
public function test(): void {
Foo::create();
Foo::create();
}
}
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(3, 28), true)
.expect("should find references");
assert_no_duplicates(&results, "static_method_references");
assert_eq!(
results.len(),
3,
"Expected 3 references (1 declaration + 2 calls), got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn class_constant_references_no_duplicates() {
let backend = create_test_backend();
let uri = "file:///tmp/test_refs_const.php";
let content = r#"<?php
class Foo {
const BAR = 42;
public function test(): void {
echo self::BAR;
echo Foo::BAR;
}
}
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(3, 10), true)
.expect("should find references");
assert_no_duplicates(&results, "class_constant_references");
assert_eq!(
results.len(),
3,
"Expected 3 references (1 declaration + 2 usages), got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn function_references_no_duplicates() {
let backend = create_test_backend();
let uri = "file:///tmp/test_refs_func.php";
let content = r#"<?php
function myHelper(): int {
return 42;
}
function test(): void {
$a = myHelper();
$b = myHelper();
}
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(2, 10), true)
.expect("should find references");
assert_no_duplicates(&results, "function_references");
assert_eq!(
results.len(),
3,
"Expected 3 references (1 declaration + 2 calls), got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn this_references_no_duplicates() {
let backend = create_test_backend();
let uri = "file:///tmp/test_refs_this.php";
let content = r#"<?php
class Foo {
public string $name = '';
public function test(): void {
$this->name = 'hello';
echo $this->name;
$this->doSomething();
}
public function doSomething(): void {}
}
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(6, 9), true)
.expect("should find references");
assert_no_duplicates(&results, "this_references");
assert_eq!(
results.len(),
3,
"Expected 3 references ($this usages), got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn cross_file_class_references_no_duplicates() {
let (backend, _dir) = crate::common::create_psr4_workspace(
r#"{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}"#,
&[
(
"src/Foo.php",
r#"<?php
namespace App;
class Foo {
public function bar(): void {}
}
"#,
),
(
"src/Baz.php",
r#"<?php
namespace App;
use App\Foo;
class Baz {
public function test(): void {
$f = new Foo();
$f->bar();
}
}
"#,
),
],
);
let foo_path = _dir.path().join("src/Foo.php");
let baz_path = _dir.path().join("src/Baz.php");
let foo_uri = format!("file://{}", foo_path.display());
let baz_uri = format!("file://{}", baz_path.display());
let foo_content = std::fs::read_to_string(&foo_path).unwrap();
let baz_content = std::fs::read_to_string(&baz_path).unwrap();
open_file(&backend, &foo_uri, &foo_content);
open_file(&backend, &baz_uri, &baz_content);
let results = backend
.find_references(&foo_uri, &foo_content, Position::new(3, 6), true)
.expect("should find references");
assert_no_duplicates(&results, "cross_file_class_references");
assert!(
results.len() >= 2,
"Expected at least 2 cross-file references, got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn cross_file_method_references_no_duplicates() {
let (backend, _dir) = crate::common::create_psr4_workspace(
r#"{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}"#,
&[
(
"src/Foo.php",
r#"<?php
namespace App;
class Foo {
public function bar(): void {}
}
"#,
),
(
"src/Baz.php",
r#"<?php
namespace App;
use App\Foo;
class Baz {
public function test(): void {
$f = new Foo();
$f->bar();
}
}
"#,
),
],
);
let foo_path = _dir.path().join("src/Foo.php");
let baz_path = _dir.path().join("src/Baz.php");
let foo_uri = format!("file://{}", foo_path.display());
let baz_uri = format!("file://{}", baz_path.display());
let foo_content = std::fs::read_to_string(&foo_path).unwrap();
let baz_content = std::fs::read_to_string(&baz_path).unwrap();
open_file(&backend, &foo_uri, &foo_content);
open_file(&backend, &baz_uri, &baz_content);
let results = backend
.find_references(&foo_uri, &foo_content, Position::new(4, 21), true)
.expect("should find references");
assert_no_duplicates(&results, "cross_file_method_references");
assert_eq!(
results.len(),
2,
"Expected 2 references (1 declaration + 1 call), got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn references_from_usage_site_no_duplicates() {
let backend = create_test_backend();
let uri = "file:///tmp/test_refs_usage.php";
let content = r#"<?php
class Foo {
public function bar(): void {}
}
class Baz {
public function test(): void {
$f = new Foo();
$f->bar();
$f->bar();
}
}
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(9, 13), true)
.expect("should find references");
assert_no_duplicates(&results, "references_from_usage");
assert_eq!(
results.len(),
3,
"Expected 3 references (1 declaration + 2 calls), got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn references_from_new_keyword_no_duplicates() {
let backend = create_test_backend();
let uri = "file:///tmp/test_refs_new.php";
let content = r#"<?php
class Foo {}
$a = new Foo();
$b = new Foo();
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(4, 10), true)
.expect("should find references");
assert_no_duplicates(&results, "references_from_new");
assert_eq!(
results.len(),
3,
"Expected 3 references, got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn class_references_in_type_hints_no_duplicates() {
let backend = create_test_backend();
let uri = "file:///tmp/test_refs_hints.php";
let content = r#"<?php
class Foo {}
class Bar {
public Foo $prop;
public function take(Foo $param): Foo {
return $param;
}
}
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(2, 6), true)
.expect("should find references");
assert_no_duplicates(&results, "class_references_in_type_hints");
assert_eq!(
results.len(),
4,
"Expected 4 references (1 decl + 3 type hints), got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn class_references_in_docblock_no_duplicates() {
let backend = create_test_backend();
let uri = "file:///tmp/test_refs_docblock.php";
let content = r#"<?php
class Foo {}
class Bar {
/**
* @param Foo $param
* @return Foo
*/
public function take(Foo $param): Foo {
return $param;
}
}
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(2, 6), true)
.expect("should find references");
assert_no_duplicates(&results, "class_references_in_docblock");
assert!(
results.len() >= 3,
"Expected at least 3 references, got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn class_references_with_extends_no_duplicates() {
let backend = create_test_backend();
let uri = "file:///tmp/test_refs_extends.php";
let content = r#"<?php
class Base {}
class Child extends Base {}
$b = new Base();
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(2, 6), true)
.expect("should find references");
assert_no_duplicates(&results, "class_references_with_extends");
assert_eq!(
results.len(),
3,
"Expected 3 references (1 decl + extends + new), got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn self_references_no_duplicates() {
let backend = create_test_backend();
let uri = "file:///tmp/test_refs_self.php";
let content = r#"<?php
class Foo {
public static function create(): self {
return new self();
}
public function test(): void {
$f = self::create();
}
}
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(3, 38), true)
.expect("should find references");
assert_no_duplicates(&results, "self_references");
assert!(
results.len() >= 2,
"Expected at least 2 references, got {}: {:#?}",
results.len(),
results
);
}
#[test]
fn debug_dump_symbol_spans_for_simple_class() {
let backend = create_test_backend();
let uri = "file:///tmp/test_debug_spans.php";
let content = r#"<?php
class Foo {
public function bar(): void {}
}
class Baz {
public function test(): void {
$f = new Foo();
$f->bar();
$f->bar();
}
}
"#;
open_file(&backend, uri, content);
let results = backend
.find_references(uri, content, Position::new(2, 6), true)
.unwrap_or_default();
eprintln!("=== Class 'Foo' references (include_declaration=true) ===");
for (i, loc) in results.iter().enumerate() {
let line = loc.range.start.line;
let col = loc.range.start.character;
let end_col = loc.range.end.character;
let source_line = content.lines().nth(line as usize).unwrap_or("");
eprintln!(
" [{}] {}:{}:{}-{} | {:?}",
i,
loc.uri,
line,
col,
end_col,
source_line.trim()
);
}
assert_no_duplicates(&results, "debug_class_refs");
let results = backend
.find_references(uri, content, Position::new(3, 21), true)
.unwrap_or_default();
eprintln!("=== Method 'bar' references (include_declaration=true) ===");
for (i, loc) in results.iter().enumerate() {
let line = loc.range.start.line;
let col = loc.range.start.character;
let end_col = loc.range.end.character;
let source_line = content.lines().nth(line as usize).unwrap_or("");
eprintln!(
" [{}] {}:{}:{}-{} | {:?}",
i,
loc.uri,
line,
col,
end_col,
source_line.trim()
);
}
assert_no_duplicates(&results, "debug_method_refs");
}
#[tokio::test]
async fn async_did_open_class_references_no_duplicates() {
use tower_lsp::LanguageServer;
use tower_lsp::lsp_types::{DidOpenTextDocumentParams, TextDocumentItem, Url};
let backend = create_test_backend();
let uri = Url::parse("file:///tmp/test_async_refs.php").unwrap();
let content = r#"<?php
class Foo {
public function bar(): void {}
}
class Baz {
public function test(): void {
$f = new Foo();
$f->bar();
$f->bar();
}
}
"#;
backend
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: uri.clone(),
language_id: "php".to_string(),
version: 1,
text: content.to_string(),
},
})
.await;
let uri_str = uri.to_string();
let results = backend
.find_references(&uri_str, content, Position::new(2, 6), true)
.expect("should find class references");
eprintln!("=== Async did_open: 'Foo' class references ===");
for (i, loc) in results.iter().enumerate() {
let line = loc.range.start.line;
let col = loc.range.start.character;
let src = content.lines().nth(line as usize).unwrap_or("");
eprintln!(" [{}] L{}:{} | {}", i, line, col, src.trim());
}
assert_no_duplicates(&results, "async_class_refs");
assert_eq!(
results.len(),
2,
"Expected 2 class refs (1 decl + 1 new Foo), got {}: {:#?}",
results.len(),
results
);
let results = backend
.find_references(&uri_str, content, Position::new(3, 21), true)
.expect("should find method references");
eprintln!("=== Async did_open: 'bar' method references ===");
for (i, loc) in results.iter().enumerate() {
let line = loc.range.start.line;
let col = loc.range.start.character;
let src = content.lines().nth(line as usize).unwrap_or("");
eprintln!(" [{}] L{}:{} | {}", i, line, col, src.trim());
}
assert_no_duplicates(&results, "async_method_refs");
assert_eq!(
results.len(),
3,
"Expected 3 method refs (1 decl + 2 calls), got {}: {:#?}",
results.len(),
results
);
}
#[tokio::test]
async fn async_did_open_cross_file_no_duplicates() {
use tower_lsp::LanguageServer;
use tower_lsp::lsp_types::{DidOpenTextDocumentParams, TextDocumentItem, Url};
let (backend, dir) = crate::common::create_psr4_workspace(
r#"{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}"#,
&[
(
"src/Product.php",
r#"<?php
namespace App;
class Product {
public function price(): int { return 0; }
}
"#,
),
(
"src/Basket.php",
r#"<?php
namespace App;
use App\Product;
class Basket {
public function addProduct(Product $p): void {
$item = new Product();
$item->price();
}
}
"#,
),
],
);
let product_path = dir.path().join("src/Product.php");
let basket_path = dir.path().join("src/Basket.php");
let product_uri = Url::from_file_path(&product_path).unwrap();
let basket_uri = Url::from_file_path(&basket_path).unwrap();
let product_content = std::fs::read_to_string(&product_path).unwrap();
let basket_content = std::fs::read_to_string(&basket_path).unwrap();
backend
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: product_uri.clone(),
language_id: "php".to_string(),
version: 1,
text: product_content.clone(),
},
})
.await;
backend
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: basket_uri.clone(),
language_id: "php".to_string(),
version: 1,
text: basket_content.clone(),
},
})
.await;
let product_uri_str = product_uri.to_string();
let results = backend
.find_references(
&product_uri_str,
&product_content,
Position::new(3, 6),
true,
)
.expect("should find class references");
eprintln!("=== Async cross-file: 'Product' class references ===");
for (i, loc) in results.iter().enumerate() {
eprintln!(
" [{}] {}:L{}:{}-{}:{}",
i,
loc.uri,
loc.range.start.line,
loc.range.start.character,
loc.range.end.line,
loc.range.end.character
);
}
assert_no_duplicates(&results, "async_cross_file_class_refs");
let results = backend
.find_references(
&product_uri_str,
&product_content,
Position::new(4, 21),
true,
)
.expect("should find method references");
eprintln!("=== Async cross-file: 'price' method references ===");
for (i, loc) in results.iter().enumerate() {
eprintln!(
" [{}] {}:L{}:{}-{}:{}",
i,
loc.uri,
loc.range.start.line,
loc.range.start.character,
loc.range.end.line,
loc.range.end.character
);
}
assert_no_duplicates(&results, "async_cross_file_method_refs");
}
#[tokio::test]
async fn async_did_open_one_file_workspace_discovers_other() {
use tower_lsp::LanguageServer;
use tower_lsp::lsp_types::{DidOpenTextDocumentParams, TextDocumentItem, Url};
let (backend, dir) = crate::common::create_psr4_workspace(
r#"{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}"#,
&[
(
"src/Widget.php",
r#"<?php
namespace App;
class Widget {
public function render(): string { return ''; }
}
"#,
),
(
"src/Dashboard.php",
r#"<?php
namespace App;
use App\Widget;
class Dashboard {
public function show(): void {
$w = new Widget();
$w->render();
$w->render();
}
}
"#,
),
],
);
let widget_path = dir.path().join("src/Widget.php");
let widget_uri = Url::from_file_path(&widget_path).unwrap();
let widget_content = std::fs::read_to_string(&widget_path).unwrap();
backend
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: widget_uri.clone(),
language_id: "php".to_string(),
version: 1,
text: widget_content.clone(),
},
})
.await;
let widget_uri_str = widget_uri.to_string();
let results = backend
.find_references(&widget_uri_str, &widget_content, Position::new(3, 6), true)
.expect("should find class references");
eprintln!("=== One-file async: 'Widget' class references ===");
for (i, loc) in results.iter().enumerate() {
eprintln!(
" [{}] {}:L{}:{}-{}:{}",
i,
loc.uri,
loc.range.start.line,
loc.range.start.character,
loc.range.end.line,
loc.range.end.character
);
}
assert_no_duplicates(&results, "async_one_file_class_refs");
let results = backend
.find_references(&widget_uri_str, &widget_content, Position::new(4, 21), true)
.expect("should find method references");
eprintln!("=== One-file async: 'render' method references ===");
for (i, loc) in results.iter().enumerate() {
eprintln!(
" [{}] {}:L{}:{}-{}:{}",
i,
loc.uri,
loc.range.start.line,
loc.range.start.character,
loc.range.end.line,
loc.range.end.character
);
}
assert_no_duplicates(&results, "async_one_file_method_refs");
}
#[test]
fn debug_dump_all_spans_for_duplicate_detection() {
let backend = create_test_backend();
let uri = "file:///tmp/test_span_dump.php";
let content = r#"<?php
namespace App;
use App\Order;
class Order {
public string $name = '';
public static int $count = 0;
const STATUS_ACTIVE = 1;
public function total(): int { return 0; }
public static function create(): self { return new self(); }
}
class Service {
public function process(Order $order): void {
$o = new Order();
$o->total();
$o->total();
$o->name;
Order::create();
Order::$count;
Order::STATUS_ACTIVE;
echo $o->name;
}
}
"#;
open_file(&backend, uri, content);
let maps = backend.open_files(); assert!(
maps.read().contains_key(uri),
"file should be in open_files"
);
let results = backend
.find_references(uri, content, Position::new(6, 6), true)
.unwrap_or_default();
eprintln!("=== 'Order' class references ===");
for (i, loc) in results.iter().enumerate() {
let line = loc.range.start.line;
let col = loc.range.start.character;
let src = content.lines().nth(line as usize).unwrap_or("");
eprintln!(" [{}] L{}:{} | {}", i, line, col, src.trim());
}
assert_no_duplicates(&results, "Order class");
let results = backend
.find_references(uri, content, Position::new(11, 21), true)
.unwrap_or_default();
eprintln!("=== 'total' method references ===");
for (i, loc) in results.iter().enumerate() {
let line = loc.range.start.line;
let col = loc.range.start.character;
let src = content.lines().nth(line as usize).unwrap_or("");
eprintln!(" [{}] L{}:{} | {}", i, line, col, src.trim());
}
assert_no_duplicates(&results, "total method");
let results = backend
.find_references(uri, content, Position::new(21, 13), true)
.unwrap_or_default();
eprintln!("=== 'name' property references ===");
for (i, loc) in results.iter().enumerate() {
let line = loc.range.start.line;
let col = loc.range.start.character;
let src = content.lines().nth(line as usize).unwrap_or("");
eprintln!(" [{}] L{}:{} | {}", i, line, col, src.trim());
}
assert_no_duplicates(&results, "name property");
let results = backend
.find_references(uri, content, Position::new(22, 14), true)
.unwrap_or_default();
eprintln!("=== 'create' static method references ===");
for (i, loc) in results.iter().enumerate() {
let line = loc.range.start.line;
let col = loc.range.start.character;
let src = content.lines().nth(line as usize).unwrap_or("");
eprintln!(" [{}] L{}:{} | {}", i, line, col, src.trim());
}
assert_no_duplicates(&results, "create static method");
let results = backend
.find_references(uri, content, Position::new(24, 16), true)
.unwrap_or_default();
eprintln!("=== 'STATUS_ACTIVE' constant references ===");
for (i, loc) in results.iter().enumerate() {
let line = loc.range.start.line;
let col = loc.range.start.character;
let src = content.lines().nth(line as usize).unwrap_or("");
eprintln!(" [{}] L{}:{} | {}", i, line, col, src.trim());
}
assert_no_duplicates(&results, "STATUS_ACTIVE constant");
let results = backend
.find_references(uri, content, Position::new(23, 12), true)
.unwrap_or_default();
eprintln!("=== '$count' static property references ===");
for (i, loc) in results.iter().enumerate() {
let line = loc.range.start.line;
let col = loc.range.start.character;
let src = content.lines().nth(line as usize).unwrap_or("");
eprintln!(" [{}] L{}:{} | {}", i, line, col, src.trim());
}
assert_no_duplicates(&results, "$count static property");
}
#[test]
fn workspace_indexed_class_references_no_duplicates() {
let (backend, dir) = crate::common::create_psr4_workspace(
r#"{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}"#,
&[
(
"src/Order.php",
r#"<?php
namespace App;
class Order {
public function total(): int { return 0; }
}
"#,
),
(
"src/Service.php",
r#"<?php
namespace App;
use App\Order;
class Service {
public function process(Order $order): void {
$o = new Order();
$o->total();
}
}
"#,
),
(
"src/Controller.php",
r#"<?php
namespace App;
use App\Order;
class Controller {
public function index(): void {
$order = new Order();
$order->total();
}
}
"#,
),
],
);
let order_path = dir.path().join("src/Order.php");
let service_path = dir.path().join("src/Service.php");
let controller_path = dir.path().join("src/Controller.php");
let order_uri = format!("file://{}", order_path.display());
let service_uri = format!("file://{}", service_path.display());
let controller_uri = format!("file://{}", controller_path.display());
let order_content = std::fs::read_to_string(&order_path).unwrap();
let service_content = std::fs::read_to_string(&service_path).unwrap();
let controller_content = std::fs::read_to_string(&controller_path).unwrap();
open_file(&backend, &order_uri, &order_content);
open_file(&backend, &service_uri, &service_content);
open_file(&backend, &controller_uri, &controller_content);
let results = backend
.find_references(&order_uri, &order_content, Position::new(3, 6), true)
.expect("should find class references");
eprintln!("=== Workspace-indexed 'Order' class references ===");
for (i, loc) in results.iter().enumerate() {
eprintln!(
" [{}] {}:{}:{}-{}:{}",
i,
loc.uri,
loc.range.start.line,
loc.range.start.character,
loc.range.end.line,
loc.range.end.character,
);
}
assert_no_duplicates(&results, "workspace_class_refs");
let results = backend
.find_references(&order_uri, &order_content, Position::new(4, 21), true)
.expect("should find method references");
eprintln!("=== Workspace-indexed 'total' method references ===");
for (i, loc) in results.iter().enumerate() {
eprintln!(
" [{}] {}:{}:{}-{}:{}",
i,
loc.uri,
loc.range.start.line,
loc.range.start.character,
loc.range.end.line,
loc.range.end.character,
);
}
assert_no_duplicates(&results, "workspace_method_refs");
}
#[test]
fn workspace_indexed_only_one_file_opened_no_duplicates() {
let (backend, dir) = crate::common::create_psr4_workspace(
r#"{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}"#,
&[
(
"src/Item.php",
r#"<?php
namespace App;
class Item {
public function price(): int { return 0; }
}
"#,
),
(
"src/Cart.php",
r#"<?php
namespace App;
use App\Item;
class Cart {
public function addItem(Item $item): void {
$i = new Item();
$i->price();
}
}
"#,
),
],
);
let item_path = dir.path().join("src/Item.php");
let item_uri = format!("file://{}", item_path.display());
let item_content = std::fs::read_to_string(&item_path).unwrap();
open_file(&backend, &item_uri, &item_content);
let results = backend
.find_references(&item_uri, &item_content, Position::new(3, 6), true)
.expect("should find class references");
eprintln!("=== One-file-opened 'Item' class references ===");
for (i, loc) in results.iter().enumerate() {
eprintln!(
" [{}] {}:{}:{}-{}:{}",
i,
loc.uri,
loc.range.start.line,
loc.range.start.character,
loc.range.end.line,
loc.range.end.character,
);
}
assert_no_duplicates(&results, "one_file_class_refs");
let results = backend
.find_references(&item_uri, &item_content, Position::new(4, 21), true)
.expect("should find method references");
eprintln!("=== One-file-opened 'price' method references ===");
for (i, loc) in results.iter().enumerate() {
eprintln!(
" [{}] {}:{}:{}-{}:{}",
i,
loc.uri,
loc.range.start.line,
loc.range.start.character,
loc.range.end.line,
loc.range.end.character,
);
}
assert_no_duplicates(&results, "one_file_method_refs");
}