use crate::common::create_test_backend;
use tower_lsp::LanguageServer;
use tower_lsp::lsp_types::*;
async fn complete_at_raw(
backend: &phpantom_lsp::Backend,
uri: &Url,
text: &str,
line: u32,
character: u32,
) -> Option<Vec<CompletionItem>> {
let open_params = DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: uri.clone(),
language_id: "php".to_string(),
version: 1,
text: text.to_string(),
},
};
backend.did_open(open_params).await;
let completion_params = CompletionParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: uri.clone() },
position: Position { line, character },
},
work_done_progress_params: WorkDoneProgressParams::default(),
partial_result_params: PartialResultParams::default(),
context: None,
};
match backend.completion(completion_params).await.unwrap() {
Some(CompletionResponse::Array(items)) => Some(items),
Some(CompletionResponse::List(list)) => Some(list.items),
None => None,
}
}
#[tokio::test]
async fn no_completion_inside_single_quoted_string() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_single.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$f = new Foo();\n",
"$s = 'hello ';\n",
);
let result = complete_at_raw(&backend, &uri, text, 5, 10).await;
assert!(
result.is_none(),
"Should suppress completion inside single-quoted string, got: {:?}",
result
);
}
#[tokio::test]
async fn no_completion_for_class_name_inside_single_quoted_string() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_single_class.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$s = 'Foo';\n",
);
let result = complete_at_raw(&backend, &uri, text, 4, 8).await;
assert!(
result.is_none(),
"Should suppress class name completion inside single-quoted string, got: {:?}",
result
);
}
#[tokio::test]
async fn no_completion_for_dollar_inside_single_quoted_string() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_single_dollar.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$f = new Foo();\n",
"$s = '$f->';\n",
);
let result = complete_at_raw(&backend, &uri, text, 5, 10).await;
assert!(
result.is_none(),
"Should suppress completion inside single-quoted string even with $f->, got: {:?}",
result
);
}
#[tokio::test]
async fn no_completion_for_plain_text_in_double_quoted_string() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_double_plain.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$f = new Foo();\n",
"$s = \"hello world\";\n",
);
let result = complete_at_raw(&backend, &uri, text, 5, 10).await;
assert!(
result.is_none(),
"Should suppress completion inside plain text in double-quoted string, got: {:?}",
result
);
}
#[tokio::test]
async fn completion_works_with_braces_in_double_quoted_string() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_double_brace.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public string $name = '';\n",
" public function bar(): void {}\n",
"}\n",
"$f = new Foo();\n",
"$s = \"hello {$f->}\";\n",
);
let result = complete_at_raw(&backend, &uri, text, 6, 17).await;
assert!(
result.is_some(),
"Should allow completion inside {{$f->}} interpolation in double-quoted string"
);
let items = result.unwrap();
let has_name = items.iter().any(|i| i.label == "name");
assert!(
has_name,
"Should suggest 'name' property. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
let has_bar = items.iter().any(|i| i.label.starts_with("bar"));
assert!(
has_bar,
"Brace interpolation should include methods. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
}
#[tokio::test]
async fn completion_works_with_simple_interpolation_in_double_quoted_string() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_double_simple.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public string $name = '';\n",
" public function bar(): void {}\n",
"}\n",
"$f = new Foo();\n",
"$s = \"hello $f->\";\n",
);
let result = complete_at_raw(&backend, &uri, text, 6, 16).await;
assert!(
result.is_some(),
"Should allow completion for simple interpolation $f-> in double-quoted string"
);
let items = result.unwrap();
let has_name = items.iter().any(|i| i.label == "name");
assert!(
has_name,
"Should suggest 'name' property. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
let has_method = items
.iter()
.any(|i| i.kind == Some(CompletionItemKind::METHOD));
assert!(
!has_method,
"Simple interpolation should NOT include methods. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
}
#[tokio::test]
async fn simple_interpolation_filters_methods_but_brace_does_not() {
let backend = create_test_backend();
let uri_simple = Url::parse("file:///str_filter_simple.php").unwrap();
let text_simple = concat!(
"<?php\n",
"class Widget {\n",
" public string $title = '';\n",
" public int $count = 0;\n",
" public function render(): string { return ''; }\n",
" public function reset(): void {}\n",
"}\n",
"$w = new Widget();\n",
"$s = \"val: $w->\";\n",
);
let simple_items = complete_at_raw(&backend, &uri_simple, text_simple, 8, 15)
.await
.expect("Simple interpolation should return items");
let simple_labels: Vec<&str> = simple_items.iter().map(|i| i.label.as_str()).collect();
assert!(
simple_labels.contains(&"title"),
"Simple: should include 'title' property. Got: {:?}",
simple_labels
);
assert!(
simple_labels.contains(&"count"),
"Simple: should include 'count' property. Got: {:?}",
simple_labels
);
assert!(
!simple_items
.iter()
.any(|i| i.kind == Some(CompletionItemKind::METHOD)),
"Simple: should NOT include any methods. Got: {:?}",
simple_labels
);
let uri_brace = Url::parse("file:///str_filter_brace.php").unwrap();
let text_brace = concat!(
"<?php\n",
"class Widget {\n",
" public string $title = '';\n",
" public int $count = 0;\n",
" public function render(): string { return ''; }\n",
" public function reset(): void {}\n",
"}\n",
"$w = new Widget();\n",
"$s = \"val: {$w->}\";\n",
);
let brace_items = complete_at_raw(&backend, &uri_brace, text_brace, 8, 16)
.await
.expect("Brace interpolation should return items");
let brace_labels: Vec<&str> = brace_items.iter().map(|i| i.label.as_str()).collect();
assert!(
brace_labels.contains(&"title"),
"Brace: should include 'title' property. Got: {:?}",
brace_labels
);
assert!(
brace_items
.iter()
.any(|i| i.kind == Some(CompletionItemKind::METHOD)),
"Brace: should include methods. Got: {:?}",
brace_labels
);
assert!(
brace_labels.iter().any(|l| l.starts_with("render")),
"Brace: should include 'render' method. Got: {:?}",
brace_labels
);
}
#[tokio::test]
async fn completion_works_with_nullsafe_simple_interpolation() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_double_nullsafe.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public string $name = '';\n",
" public function bar(): void {}\n",
"}\n",
"$f = new Foo();\n",
"$s = \"hello $f?->\";\n",
);
let result = complete_at_raw(&backend, &uri, text, 6, 17).await;
assert!(
result.is_some(),
"Should allow completion for nullsafe interpolation $f?-> in double-quoted string"
);
let items = result.unwrap();
let has_name = items.iter().any(|i| i.label == "name");
assert!(
has_name,
"Should suggest 'name' property. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
let has_method = items
.iter()
.any(|i| i.kind == Some(CompletionItemKind::METHOD));
assert!(
!has_method,
"Nullsafe simple interpolation should NOT include methods. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
}
#[tokio::test]
async fn no_completion_for_plain_text_in_heredoc() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_heredoc_plain.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$f = new Foo();\n",
"$s = <<<EOT\n",
"hello world\n",
"EOT;\n",
);
let result = complete_at_raw(&backend, &uri, text, 6, 5).await;
assert!(
result.is_none(),
"Should suppress completion inside plain text in heredoc, got: {:?}",
result
);
}
#[tokio::test]
async fn completion_works_with_braces_in_heredoc() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_heredoc_brace.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public string $name = '';\n",
" public function bar(): void {}\n",
"}\n",
"$f = new Foo();\n",
"$s = <<<EOT\n",
"hello {$f->}\n",
"EOT;\n",
);
let result = complete_at_raw(&backend, &uri, text, 7, 11).await;
assert!(
result.is_some(),
"Should allow completion inside {{$f->}} interpolation in heredoc"
);
let items = result.unwrap();
let has_name = items.iter().any(|i| i.label == "name");
assert!(
has_name,
"Should suggest 'name' property inside heredoc interpolation. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
let has_bar = items.iter().any(|i| i.label.starts_with("bar"));
assert!(
has_bar,
"Brace interpolation in heredoc should include methods. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
}
#[tokio::test]
async fn completion_works_with_simple_interpolation_in_heredoc() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_heredoc_simple.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public string $name = '';\n",
" public function bar(): void {}\n",
"}\n",
"$f = new Foo();\n",
"$s = <<<EOT\n",
"hello $f->\n",
"EOT;\n",
);
let result = complete_at_raw(&backend, &uri, text, 7, 10).await;
assert!(
result.is_some(),
"Should allow completion for simple interpolation $f-> in heredoc"
);
let items = result.unwrap();
let has_name = items.iter().any(|i| i.label == "name");
assert!(
has_name,
"Should suggest 'name' property inside heredoc simple interpolation. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
let has_method = items
.iter()
.any(|i| i.kind == Some(CompletionItemKind::METHOD));
assert!(
!has_method,
"Simple interpolation in heredoc should NOT include methods. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
}
#[tokio::test]
async fn no_completion_inside_nowdoc() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_nowdoc.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$f = new Foo();\n",
"$s = <<<'EOT'\n",
"hello world\n",
"EOT;\n",
);
let result = complete_at_raw(&backend, &uri, text, 6, 5).await;
assert!(
result.is_none(),
"Should suppress completion inside nowdoc, got: {:?}",
result
);
}
#[tokio::test]
async fn no_completion_for_dollar_arrow_inside_nowdoc() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_nowdoc_arrow.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$f = new Foo();\n",
"$s = <<<'EOT'\n",
"$f->\n",
"EOT;\n",
);
let result = complete_at_raw(&backend, &uri, text, 6, 4).await;
assert!(
result.is_none(),
"Should suppress completion inside nowdoc even with $f->, got: {:?}",
result
);
}
#[tokio::test]
async fn completion_works_after_single_quoted_string() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_after_single.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$s = 'hello';\n",
"$f = new Foo();\n",
"$f->\n",
);
let result = complete_at_raw(&backend, &uri, text, 6, 4).await;
assert!(
result.is_some(),
"Should still offer completions in normal code after a string"
);
let items = result.unwrap();
let has_bar = items.iter().any(|i| i.label.starts_with("bar"));
assert!(
has_bar,
"Should suggest 'bar'. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
}
#[tokio::test]
async fn completion_works_after_double_quoted_string() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_after_double.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$s = \"hello\";\n",
"$f = new Foo();\n",
"$f->\n",
);
let result = complete_at_raw(&backend, &uri, text, 6, 4).await;
assert!(
result.is_some(),
"Should still offer completions in normal code after a double-quoted string"
);
let items = result.unwrap();
let has_bar = items.iter().any(|i| i.label.starts_with("bar"));
assert!(
has_bar,
"Should suggest 'bar'. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
}
#[tokio::test]
async fn completion_works_after_heredoc() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_after_heredoc.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$s = <<<EOT\n",
"hello\n",
"EOT;\n",
"$f = new Foo();\n",
"$f->\n",
);
let result = complete_at_raw(&backend, &uri, text, 8, 4).await;
assert!(
result.is_some(),
"Should still offer completions in normal code after a heredoc"
);
let items = result.unwrap();
let has_bar = items.iter().any(|i| i.label.starts_with("bar"));
assert!(
has_bar,
"Should suggest 'bar'. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
}
#[tokio::test]
async fn completion_works_after_nowdoc() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_after_nowdoc.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$s = <<<'EOT'\n",
"hello\n",
"EOT;\n",
"$f = new Foo();\n",
"$f->\n",
);
let result = complete_at_raw(&backend, &uri, text, 8, 4).await;
assert!(
result.is_some(),
"Should still offer completions in normal code after a nowdoc"
);
let items = result.unwrap();
let has_bar = items.iter().any(|i| i.label.starts_with("bar"));
assert!(
has_bar,
"Should suggest 'bar'. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
}
#[tokio::test]
async fn variable_completion_inside_double_quoted_string_after_dollar_var() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_double_var_no_arrow.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$f = new Foo();\n",
"$s = \"hello $f\";\n",
);
let result = complete_at_raw(&backend, &uri, text, 5, 14).await;
assert!(
result.is_some(),
"Should allow completion for $var inside double-quoted string (interpolation site)"
);
}
#[tokio::test]
async fn variable_completion_inside_double_quoted_string_bare_dollar() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_double_bare_dollar.php").unwrap();
let text = concat!("<?php\n", "$foo = 42;\n", "$s = \"hello $\";\n",);
let result = complete_at_raw(&backend, &uri, text, 2, 13).await;
assert!(
result.is_some(),
"Should allow variable completion for bare $ inside double-quoted string"
);
let items = result.unwrap();
let has_foo = items.iter().any(|i| i.label == "$foo");
assert!(
has_foo,
"Should suggest '$foo'. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
}
#[tokio::test]
async fn variable_completion_inside_heredoc_bare_dollar() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_heredoc_bare_dollar.php").unwrap();
let text = concat!(
"<?php\n",
"$foo = 42;\n",
"$s = <<<EOT\n",
"hello $\n",
"EOT;\n",
);
let result = complete_at_raw(&backend, &uri, text, 3, 7).await;
assert!(
result.is_some(),
"Should allow variable completion for bare $ inside heredoc"
);
let items = result.unwrap();
let has_foo = items.iter().any(|i| i.label == "$foo");
assert!(
has_foo,
"Should suggest '$foo' inside heredoc. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
}
#[tokio::test]
async fn no_variable_completion_for_dollar_inside_single_quoted_string() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_single_bare_dollar.php").unwrap();
let text = concat!("<?php\n", "$foo = 42;\n", "$s = 'hello $';\n",);
let result = complete_at_raw(&backend, &uri, text, 2, 13).await;
assert!(
result.is_none(),
"Should suppress variable completion for $ inside single-quoted string, got: {:?}",
result
);
}
#[tokio::test]
async fn no_variable_completion_for_dollar_inside_nowdoc() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_nowdoc_bare_dollar.php").unwrap();
let text = concat!(
"<?php\n",
"$foo = 42;\n",
"$s = <<<'EOT'\n",
"hello $\n",
"EOT;\n",
);
let result = complete_at_raw(&backend, &uri, text, 3, 7).await;
assert!(
result.is_none(),
"Should suppress variable completion for $ inside nowdoc, got: {:?}",
result
);
}
#[tokio::test]
async fn escaped_quote_does_not_end_string() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_escaped_quote.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$f = new Foo();\n",
"$s = \"she said \\\"hello\\\" ok\";\n",
);
let result = complete_at_raw(&backend, &uri, text, 5, 22).await;
assert!(
result.is_none(),
"Escaped quotes should not end the string early, got: {:?}",
result
);
}
#[tokio::test]
async fn escaped_single_quote_does_not_end_string() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_escaped_single.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$f = new Foo();\n",
"$s = 'she said \\'hello\\' ok';\n",
);
let result = complete_at_raw(&backend, &uri, text, 5, 22).await;
assert!(
result.is_none(),
"Escaped single quotes should not end the string early, got: {:?}",
result
);
}
#[tokio::test]
async fn comment_syntax_inside_string_still_suppressed() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_comment_inside.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$s = '// not a comment';\n",
"$f = new Foo();\n",
"$f->\n",
);
let result = complete_at_raw(&backend, &uri, text, 6, 4).await;
assert!(
result.is_some(),
"Comment syntax inside a string should not suppress later completions"
);
let items = result.unwrap();
let has_bar = items.iter().any(|i| i.label.starts_with("bar"));
assert!(
has_bar,
"Should still suggest 'bar'. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
}
#[tokio::test]
async fn no_class_name_completion_inside_brace_interpolation() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_brace_no_class.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$f = new Foo();\n",
"$s = \"hello {Foo}\";\n",
);
let result = complete_at_raw(&backend, &uri, text, 5, 12).await;
assert!(
result.is_none(),
"Should suppress class name completion inside non-interpolation braces, got: {:?}",
result
);
}
#[tokio::test]
async fn no_completion_for_non_interpolation_braces_in_double_quoted_string() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_double_brace_no_dollar.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" const BAR = 1;\n",
"}\n",
"$s = \"{Foo::}\";\n",
);
let result = complete_at_raw(&backend, &uri, text, 4, 12).await;
assert!(
result.is_none(),
"Should suppress completion inside {{ClassName::}} without $ — not interpolation, got: {:?}",
result
);
}
#[tokio::test]
async fn no_class_name_completion_inside_double_quoted_string() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_double_class.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$s = \"Foo\";\n",
);
let result = complete_at_raw(&backend, &uri, text, 4, 8).await;
assert!(
result.is_none(),
"Should suppress class name completion inside double-quoted string, got: {:?}",
result
);
}
#[tokio::test]
async fn concatenation_after_string_works() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_concat.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$s = 'hello';\n",
"$f = new Foo();\n",
"$f->\n",
);
let result = complete_at_raw(&backend, &uri, text, 6, 4).await;
assert!(
result.is_some(),
"Code after a closed string should complete normally"
);
}
#[test]
fn unit_classify_nested_braces_in_interpolation() {
use phpantom_lsp::completion::comment_position::{StringContext, classify_string_context};
let content = "<?php\n$s = \"{$arr['key']->}\";\n";
let pos = Position {
line: 1,
character: 20,
};
assert_eq!(
classify_string_context(content, pos),
StringContext::BraceInterpolation
);
}
#[tokio::test]
async fn multiple_strings_on_same_line() {
let backend = create_test_backend();
let uri = Url::parse("file:///str_multi_same_line.php").unwrap();
let text = concat!(
"<?php\n",
"class Foo {\n",
" public function bar(): void {}\n",
"}\n",
"$f = new Foo();\n",
"$a = 'x'; $b = 'y'; $f->\n",
);
let result = complete_at_raw(&backend, &uri, text, 5, 25).await;
assert!(
result.is_some(),
"Should complete after multiple closed strings on the same line"
);
let items = result.unwrap();
let has_bar = items.iter().any(|i| i.label.starts_with("bar"));
assert!(
has_bar,
"Should suggest 'bar'. Got: {:?}",
items.iter().map(|i| &i.label).collect::<Vec<_>>()
);
}
#[test]
fn unit_classify_not_in_string() {
use phpantom_lsp::completion::comment_position::{StringContext, classify_string_context};
let content = "<?php\n$x = 1;\n";
let pos = Position {
line: 1,
character: 5,
};
assert_eq!(
classify_string_context(content, pos),
StringContext::NotInString
);
}
#[test]
fn unit_classify_single_quoted() {
use phpantom_lsp::completion::comment_position::{StringContext, classify_string_context};
let content = "<?php\n$x = 'hello';\n";
let pos = Position {
line: 1,
character: 8,
};
assert_eq!(
classify_string_context(content, pos),
StringContext::InStringLiteral
);
}
#[test]
fn unit_classify_double_quoted_plain() {
use phpantom_lsp::completion::comment_position::{StringContext, classify_string_context};
let content = "<?php\n$x = \"hello\";\n";
let pos = Position {
line: 1,
character: 8,
};
assert_eq!(
classify_string_context(content, pos),
StringContext::InStringLiteral
);
}
#[test]
fn unit_classify_double_quoted_brace_interpolation() {
use phpantom_lsp::completion::comment_position::{StringContext, classify_string_context};
let content = "<?php\n$x = \"{$f->}\";\n";
let pos = Position {
line: 1,
character: 11,
};
assert_eq!(
classify_string_context(content, pos),
StringContext::BraceInterpolation
);
}
#[test]
fn unit_classify_double_quoted_simple_interpolation() {
use phpantom_lsp::completion::comment_position::{StringContext, classify_string_context};
let content = "<?php\n$x = \"hi $f->\";\n";
let pos = Position {
line: 1,
character: 13,
};
assert_eq!(
classify_string_context(content, pos),
StringContext::SimpleInterpolation
);
}
#[test]
fn unit_classify_double_quoted_bare_dollar() {
use phpantom_lsp::completion::comment_position::{StringContext, classify_string_context};
let content = "<?php\n$x = \"hi $\";\n";
let pos = Position {
line: 1,
character: 10,
};
assert_eq!(
classify_string_context(content, pos),
StringContext::SimpleInterpolation
);
}
#[test]
fn unit_classify_double_quoted_partial_variable() {
use phpantom_lsp::completion::comment_position::{StringContext, classify_string_context};
let content = "<?php\n$x = \"hi $fo\";\n";
let pos = Position {
line: 1,
character: 12,
};
assert_eq!(
classify_string_context(content, pos),
StringContext::SimpleInterpolation
);
}
#[test]
fn unit_classify_nowdoc() {
use phpantom_lsp::completion::comment_position::{StringContext, classify_string_context};
let content = "<?php\n$x = <<<'EOT'\nhello\nEOT;\n";
let pos = Position {
line: 2,
character: 3,
};
assert_eq!(
classify_string_context(content, pos),
StringContext::InStringLiteral
);
}
#[test]
fn unit_classify_heredoc_plain() {
use phpantom_lsp::completion::comment_position::{StringContext, classify_string_context};
let content = "<?php\n$x = <<<EOT\nhello\nEOT;\n";
let pos = Position {
line: 2,
character: 3,
};
assert_eq!(
classify_string_context(content, pos),
StringContext::InStringLiteral
);
}
#[test]
fn unit_classify_heredoc_brace_interpolation() {
use phpantom_lsp::completion::comment_position::{StringContext, classify_string_context};
let content = "<?php\n$x = <<<EOT\n{$f->}\nEOT;\n";
let pos = Position {
line: 2,
character: 5,
};
assert_eq!(
classify_string_context(content, pos),
StringContext::BraceInterpolation
);
}
#[test]
fn unit_classify_after_string() {
use phpantom_lsp::completion::comment_position::{StringContext, classify_string_context};
let content = "<?php\n$x = 'hello'; $y = 1;\n";
let pos = Position {
line: 1,
character: 20,
};
assert_eq!(
classify_string_context(content, pos),
StringContext::NotInString
);
}