use super::*;
use expect_test::expect;
async fn check_invariant(s: &mut TestServer, path: &str, src: &str, line: u32, character: u32) {
s.open(path, src).await;
let resp = s.linked_editing_range(path, line, character).await;
assert_linked_editing_ranges_share_text(&resp, src);
}
#[tokio::test]
async fn linked_ranges_cover_same_text_across_fixtures() {
let mut s = TestServer::new().await;
check_invariant(
&mut s,
"fn.php",
"<?php\nfunction greet() {}\ngreet();\ngreet();\n",
1,
12,
)
.await;
let mut s = TestServer::new().await;
check_invariant(
&mut s,
"method.php",
"<?php\nclass Calc {\n public function add(): void {}\n public function self_call(): void { $this->add(); }\n}\n",
2,
22,
)
.await;
let mut s = TestServer::new().await;
check_invariant(
&mut s,
"var.php",
"<?php\nfunction f(): void {\n $foo = 1;\n echo $foo;\n $foo += 2;\n}\n",
2,
6,
)
.await;
let mut s = TestServer::new().await;
check_invariant(
&mut s,
"cjk.php",
"<?php\nfunction 名前() {}\n名前();\n",
1,
10,
)
.await;
}
#[tokio::test]
async fn class_with_only_declaration_yields_one_range() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range("<?php\nclass Lin$0kedClass {}\n")
.await;
expect![[r#"
1:6-1:17
pattern: [a-zA-Z_\u00A0-\uFFFF][a-zA-Z0-9_\u00A0-\uFFFF]*"#]]
.assert_eq(&out);
}
#[tokio::test]
async fn function_decl_links_to_all_call_sites() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range(
r#"<?php
function gre$0et() {}
greet();
greet();
"#,
)
.await;
expect![[r#"
1:9-1:14
2:0-2:5
3:0-3:5
pattern: [a-zA-Z_\u00A0-\uFFFF][a-zA-Z0-9_\u00A0-\uFFFF]*"#]]
.assert_eq(&out);
}
#[tokio::test]
async fn function_call_links_back_to_decl() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range(
r#"<?php
function greet() {}
gr$0eet();
"#,
)
.await;
expect![[r#"
1:9-1:14
2:0-2:5
pattern: [a-zA-Z_\u00A0-\uFFFF][a-zA-Z0-9_\u00A0-\uFFFF]*"#]]
.assert_eq(&out);
}
#[tokio::test]
async fn class_decl_and_new_expression() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range("<?php\nclass F$0oo {}\n$x = new Foo();\n")
.await;
expect![[r#"
1:6-1:9
2:9-2:12
pattern: [a-zA-Z_\u00A0-\uFFFF][a-zA-Z0-9_\u00A0-\uFFFF]*"#]]
.assert_eq(&out);
}
#[tokio::test]
async fn method_decl_and_call() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range(
r#"<?php
class Calc {
public function ad$0d(): void {}
}
$c = new Calc();
$c->add();
"#,
)
.await;
expect![[r#"
2:20-2:23
5:4-5:7
pattern: [a-zA-Z_\u00A0-\uFFFF][a-zA-Z0-9_\u00A0-\uFFFF]*"#]]
.assert_eq(&out);
}
#[tokio::test]
async fn variable_in_scope_links_all_occurrences_with_dollar_pattern() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range(
r#"<?php
function f(): void {
$fo$0o = 1;
echo $foo;
$foo += 2;
}
"#,
)
.await;
expect![[r#"
2:4-2:8
3:9-3:13
4:4-4:8
pattern: \$[a-zA-Z_\u00A0-\uFFFF][a-zA-Z0-9_\u00A0-\uFFFF]*"#]]
.assert_eq(&out);
}
#[tokio::test]
async fn variable_does_not_cross_function_scope() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range(
r#"<?php
function f() { $x$0 = 1; }
function g() { $x = 2; }
"#,
)
.await;
expect![[r#"
1:15-1:17
pattern: \$[a-zA-Z_\u00A0-\uFFFF][a-zA-Z0-9_\u00A0-\uFFFF]*"#]]
.assert_eq(&out);
}
#[tokio::test]
async fn cursor_on_dollar_sign_still_finds_variable() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range("<?php\nfunction f() { $0$x = 1; echo $x; }\n")
.await;
expect![[r#"
1:15-1:17
1:28-1:30
pattern: \$[a-zA-Z_\u00A0-\uFFFF][a-zA-Z0-9_\u00A0-\uFFFF]*"#]]
.assert_eq(&out);
}
#[tokio::test]
async fn whitespace_returns_no_linked_editing() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range("<?php\nclass Foo {} $0 $x = 1;\n")
.await;
expect!["<no linked editing>"].assert_eq(&out);
}
#[tokio::test]
async fn unknown_word_returns_no_linked_editing() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range("<?php\necho 'nob$0ody';\n")
.await;
expect!["<no linked editing>"].assert_eq(&out);
}
#[tokio::test]
async fn comment_word_matching_class_name_does_not_link() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range("<?php\n// uses Fo$0o here\nclass Foo {}\n$x = new Foo();\n")
.await;
expect!["<no linked editing>"].assert_eq(&out);
}
#[tokio::test]
async fn string_literal_word_matching_function_name_does_not_link() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range("<?php\nfunction greet() {}\n$x = 'gr$0eet';\ngreet();\n")
.await;
expect!["<no linked editing>"].assert_eq(&out);
}
#[tokio::test]
async fn non_variable_pattern_disallows_dollar_sign() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range("<?php\nclass Fo$0o {}\n")
.await;
let pattern = out
.lines()
.find_map(|l| l.strip_prefix("pattern: "))
.expect("response should include a wordPattern");
assert!(
!pattern.contains('$') || pattern.contains(r"\$"),
"non-variable pattern must not allow leading $; got {pattern:?}"
);
}
#[tokio::test]
async fn variable_pattern_requires_dollar_sign() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range("<?php\nfunction f() { $x$0 = 1; }\n")
.await;
let pattern = out
.lines()
.find_map(|l| l.strip_prefix("pattern: "))
.expect("response should include a wordPattern");
assert!(
pattern.starts_with(r"\$"),
"variable pattern must require leading \\$; got {pattern:?}"
);
}
#[tokio::test]
async fn method_in_one_class_does_not_link_unrelated_class_with_same_name() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range(
r#"<?php
class A {
public function ba$0r(): void {}
}
class B {
public function bar(): void {}
}
"#,
)
.await;
expect![[r#"
2:20-2:23
pattern: [a-zA-Z_\u00A0-\uFFFF][a-zA-Z0-9_\u00A0-\uFFFF]*"#]]
.assert_eq(&out);
}
#[tokio::test]
async fn class_name_itself_still_links_globally() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range("<?php\nclass Fo$0o {}\n$x = new Foo();\n")
.await;
expect![[r#"
1:6-1:9
2:9-2:12
pattern: [a-zA-Z_\u00A0-\uFFFF][a-zA-Z0-9_\u00A0-\uFFFF]*"#]]
.assert_eq(&out);
}
#[tokio::test]
async fn cjk_identifier_links_correctly() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range("<?php\nfunction 名$0前() {}\n名前();\n")
.await;
expect![[r#"
1:9-1:11
2:0-2:2
pattern: [a-zA-Z_\u00A0-\uFFFF][a-zA-Z0-9_\u00A0-\uFFFF]*"#]]
.assert_eq(&out);
}
#[tokio::test]
async fn utf8_identifier_links_correctly() {
let mut s = TestServer::new().await;
let out = s
.check_linked_editing_range("<?php\nfunction caf$0é() {}\ncafé();\n")
.await;
expect![[r#"
1:9-1:13
2:0-2:4
pattern: [a-zA-Z_\u00A0-\uFFFF][a-zA-Z0-9_\u00A0-\uFFFF]*"#]]
.assert_eq(&out);
}