mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-26 11:55:04 +00:00
organize completion tests better
This commit is contained in:
parent
d4ef07b235
commit
45232dfa68
5 changed files with 488 additions and 393 deletions
|
@ -16,7 +16,7 @@ use hir::source_binder;
|
||||||
use crate::{
|
use crate::{
|
||||||
db,
|
db,
|
||||||
Cancelable, FilePosition,
|
Cancelable, FilePosition,
|
||||||
completion::completion_item::Completions,
|
completion::completion_item::{Completions, CompletionKind},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::completion::completion_item::{CompletionItem, InsertText};
|
pub use crate::completion::completion_item::{CompletionItem, InsertText};
|
||||||
|
@ -81,7 +81,12 @@ fn param_completions(acc: &mut Completions, ctx: SyntaxNodeRef) {
|
||||||
Some((label, lookup))
|
Some((label, lookup))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.for_each(|(label, lookup)| CompletionItem::new(label).lookup_by(lookup).add_to(acc));
|
.for_each(|(label, lookup)| {
|
||||||
|
CompletionItem::new(label)
|
||||||
|
.lookup_by(lookup)
|
||||||
|
.kind(CompletionKind::Magic)
|
||||||
|
.add_to(acc)
|
||||||
|
});
|
||||||
|
|
||||||
fn process<'a, N: ast::FnDefOwner<'a>>(
|
fn process<'a, N: ast::FnDefOwner<'a>>(
|
||||||
node: N,
|
node: N,
|
||||||
|
@ -104,342 +109,62 @@ fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn check_completion(code: &str, expected_completions: &str, kind: CompletionKind) {
|
||||||
|
use crate::mock_analysis::{single_file_with_position, analysis_and_position};
|
||||||
|
let (analysis, position) = if code.contains("//-") {
|
||||||
|
analysis_and_position(code)
|
||||||
|
} else {
|
||||||
|
single_file_with_position(code)
|
||||||
|
};
|
||||||
|
let completions = completions(&analysis.imp.db, position).unwrap().unwrap();
|
||||||
|
completions.assert_match(expected_completions, kind);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use test_utils::assert_eq_dbg;
|
|
||||||
|
|
||||||
use crate::mock_analysis::single_file_with_position;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn is_snippet(completion_item: &CompletionItem) -> bool {
|
fn check_magic_completion(code: &str, expected_completions: &str) {
|
||||||
match completion_item.insert_text() {
|
check_completion(code, expected_completions, CompletionKind::Magic);
|
||||||
InsertText::Snippet { .. } => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_scope_completion(code: &str, expected_completions: &str) {
|
|
||||||
let (analysis, position) = single_file_with_position(code);
|
|
||||||
let completions = completions(&analysis.imp.db, position)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
|
||||||
.filter(|c| !is_snippet(c))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
assert_eq_dbg(expected_completions, &completions);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_snippet_completion(code: &str, expected_completions: &str) {
|
|
||||||
let (analysis, position) = single_file_with_position(code);
|
|
||||||
let completions = completions(&analysis.imp.db, position)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
|
||||||
.filter(is_snippet)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
assert_eq_dbg(expected_completions, &completions);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_completion_let_scope() {
|
|
||||||
check_scope_completion(
|
|
||||||
r"
|
|
||||||
fn quux(x: i32) {
|
|
||||||
let y = 92;
|
|
||||||
1 + <|>;
|
|
||||||
let z = ();
|
|
||||||
}
|
|
||||||
",
|
|
||||||
r#"[CompletionItem { label: "y", lookup: None, snippet: None },
|
|
||||||
CompletionItem { label: "x", lookup: None, snippet: None },
|
|
||||||
CompletionItem { label: "quux", lookup: None, snippet: None }]"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_completion_if_let_scope() {
|
|
||||||
check_scope_completion(
|
|
||||||
r"
|
|
||||||
fn quux() {
|
|
||||||
if let Some(x) = foo() {
|
|
||||||
let y = 92;
|
|
||||||
};
|
|
||||||
if let Some(a) = bar() {
|
|
||||||
let b = 62;
|
|
||||||
1 + <|>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
",
|
|
||||||
r#"[CompletionItem { label: "b", lookup: None, snippet: None },
|
|
||||||
CompletionItem { label: "a", lookup: None, snippet: None },
|
|
||||||
CompletionItem { label: "quux", lookup: None, snippet: None }]"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_completion_for_scope() {
|
|
||||||
check_scope_completion(
|
|
||||||
r"
|
|
||||||
fn quux() {
|
|
||||||
for x in &[1, 2, 3] {
|
|
||||||
<|>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
",
|
|
||||||
r#"[CompletionItem { label: "x", lookup: None, snippet: None },
|
|
||||||
CompletionItem { label: "quux", lookup: None, snippet: None }]"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_completion_mod_scope() {
|
|
||||||
check_scope_completion(
|
|
||||||
r"
|
|
||||||
struct Foo;
|
|
||||||
enum Baz {}
|
|
||||||
fn quux() {
|
|
||||||
<|>
|
|
||||||
}
|
|
||||||
",
|
|
||||||
r#"[CompletionItem { label: "quux", lookup: None, snippet: None },
|
|
||||||
CompletionItem { label: "Foo", lookup: None, snippet: None },
|
|
||||||
CompletionItem { label: "Baz", lookup: None, snippet: None }]"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_completion_mod_scope_no_self_use() {
|
|
||||||
check_scope_completion(
|
|
||||||
r"
|
|
||||||
use foo<|>;
|
|
||||||
",
|
|
||||||
r#"[]"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_completion_self_path() {
|
|
||||||
check_scope_completion(
|
|
||||||
r"
|
|
||||||
use self::m::<|>;
|
|
||||||
|
|
||||||
mod m {
|
|
||||||
struct Bar;
|
|
||||||
}
|
|
||||||
",
|
|
||||||
r#"[CompletionItem { label: "Bar", lookup: None, snippet: None }]"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_completion_mod_scope_nested() {
|
|
||||||
check_scope_completion(
|
|
||||||
r"
|
|
||||||
struct Foo;
|
|
||||||
mod m {
|
|
||||||
struct Bar;
|
|
||||||
fn quux() { <|> }
|
|
||||||
}
|
|
||||||
",
|
|
||||||
r#"[CompletionItem { label: "quux", lookup: None, snippet: None },
|
|
||||||
CompletionItem { label: "Bar", lookup: None, snippet: None }]"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_complete_type() {
|
|
||||||
check_scope_completion(
|
|
||||||
r"
|
|
||||||
struct Foo;
|
|
||||||
fn x() -> <|>
|
|
||||||
",
|
|
||||||
r#"[CompletionItem { label: "Foo", lookup: None, snippet: None },
|
|
||||||
CompletionItem { label: "x", lookup: None, snippet: None }]"#,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_complete_shadowing() {
|
|
||||||
check_scope_completion(
|
|
||||||
r"
|
|
||||||
fn foo() -> {
|
|
||||||
let bar = 92;
|
|
||||||
{
|
|
||||||
let bar = 62;
|
|
||||||
<|>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
",
|
|
||||||
r#"[CompletionItem { label: "bar", lookup: None, snippet: None },
|
|
||||||
CompletionItem { label: "foo", lookup: None, snippet: None }]"#,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_complete_self() {
|
|
||||||
check_scope_completion(
|
|
||||||
r"
|
|
||||||
impl S { fn foo(&self) { <|> } }
|
|
||||||
",
|
|
||||||
r#"[CompletionItem { label: "self", lookup: None, snippet: None }]"#,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_completion_kewords() {
|
|
||||||
check_snippet_completion(r"
|
|
||||||
fn quux() {
|
|
||||||
<|>
|
|
||||||
}
|
|
||||||
", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
|
|
||||||
CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
|
|
||||||
CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
|
|
||||||
CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
|
|
||||||
CompletionItem { label: "return", lookup: None, snippet: Some("return") },
|
|
||||||
CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
|
|
||||||
CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_completion_else() {
|
|
||||||
check_snippet_completion(r"
|
|
||||||
fn quux() {
|
|
||||||
if true {
|
|
||||||
()
|
|
||||||
} <|>
|
|
||||||
}
|
|
||||||
", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
|
|
||||||
CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
|
|
||||||
CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
|
|
||||||
CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
|
|
||||||
CompletionItem { label: "else", lookup: None, snippet: Some("else {$0}") },
|
|
||||||
CompletionItem { label: "else if", lookup: None, snippet: Some("else if $0 {}") },
|
|
||||||
CompletionItem { label: "return", lookup: None, snippet: Some("return") },
|
|
||||||
CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
|
|
||||||
CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_completion_return_value() {
|
|
||||||
check_snippet_completion(r"
|
|
||||||
fn quux() -> i32 {
|
|
||||||
<|>
|
|
||||||
92
|
|
||||||
}
|
|
||||||
", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
|
|
||||||
CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
|
|
||||||
CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
|
|
||||||
CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
|
|
||||||
CompletionItem { label: "return", lookup: None, snippet: Some("return $0;") },
|
|
||||||
CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
|
|
||||||
CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
|
|
||||||
check_snippet_completion(r"
|
|
||||||
fn quux() {
|
|
||||||
<|>
|
|
||||||
92
|
|
||||||
}
|
|
||||||
", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
|
|
||||||
CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
|
|
||||||
CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
|
|
||||||
CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
|
|
||||||
CompletionItem { label: "return", lookup: None, snippet: Some("return;") },
|
|
||||||
CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
|
|
||||||
CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_completion_return_no_stmt() {
|
|
||||||
check_snippet_completion(r"
|
|
||||||
fn quux() -> i32 {
|
|
||||||
match () {
|
|
||||||
() => <|>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
|
|
||||||
CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
|
|
||||||
CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
|
|
||||||
CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
|
|
||||||
CompletionItem { label: "return", lookup: None, snippet: Some("return $0") },
|
|
||||||
CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
|
|
||||||
CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_continue_break_completion() {
|
|
||||||
check_snippet_completion(r"
|
|
||||||
fn quux() -> i32 {
|
|
||||||
loop { <|> }
|
|
||||||
}
|
|
||||||
", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
|
|
||||||
CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
|
|
||||||
CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
|
|
||||||
CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
|
|
||||||
CompletionItem { label: "continue", lookup: None, snippet: Some("continue") },
|
|
||||||
CompletionItem { label: "break", lookup: None, snippet: Some("break") },
|
|
||||||
CompletionItem { label: "return", lookup: None, snippet: Some("return $0") },
|
|
||||||
CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
|
|
||||||
CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
|
|
||||||
check_snippet_completion(r"
|
|
||||||
fn quux() -> i32 {
|
|
||||||
loop { || { <|> } }
|
|
||||||
}
|
|
||||||
", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
|
|
||||||
CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
|
|
||||||
CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
|
|
||||||
CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
|
|
||||||
CompletionItem { label: "return", lookup: None, snippet: Some("return $0") },
|
|
||||||
CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
|
|
||||||
CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_param_completion_last_param() {
|
fn test_param_completion_last_param() {
|
||||||
check_scope_completion(r"
|
check_magic_completion(
|
||||||
|
r"
|
||||||
fn foo(file_id: FileId) {}
|
fn foo(file_id: FileId) {}
|
||||||
fn bar(file_id: FileId) {}
|
fn bar(file_id: FileId) {}
|
||||||
fn baz(file<|>) {}
|
fn baz(file<|>) {}
|
||||||
", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#);
|
",
|
||||||
|
r#"file_id "file_id: FileId""#,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_param_completion_nth_param() {
|
fn test_param_completion_nth_param() {
|
||||||
check_scope_completion(r"
|
check_magic_completion(
|
||||||
|
r"
|
||||||
fn foo(file_id: FileId) {}
|
fn foo(file_id: FileId) {}
|
||||||
fn bar(file_id: FileId) {}
|
fn bar(file_id: FileId) {}
|
||||||
fn baz(file<|>, x: i32) {}
|
fn baz(file<|>, x: i32) {}
|
||||||
", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#);
|
",
|
||||||
|
r#"file_id "file_id: FileId""#,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_param_completion_trait_param() {
|
fn test_param_completion_trait_param() {
|
||||||
check_scope_completion(r"
|
check_magic_completion(
|
||||||
|
r"
|
||||||
pub(crate) trait SourceRoot {
|
pub(crate) trait SourceRoot {
|
||||||
pub fn contains(&self, file_id: FileId) -> bool;
|
pub fn contains(&self, file_id: FileId) -> bool;
|
||||||
pub fn module_map(&self) -> &ModuleMap;
|
pub fn module_map(&self) -> &ModuleMap;
|
||||||
pub fn lines(&self, file_id: FileId) -> &LineIndex;
|
pub fn lines(&self, file_id: FileId) -> &LineIndex;
|
||||||
pub fn syntax(&self, file<|>)
|
pub fn syntax(&self, file<|>)
|
||||||
}
|
}
|
||||||
", r#"[CompletionItem { label: "self", lookup: None, snippet: None },
|
|
||||||
CompletionItem { label: "SourceRoot", lookup: None, snippet: None },
|
|
||||||
CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_item_snippets() {
|
|
||||||
// check_snippet_completion(r"
|
|
||||||
// <|>
|
|
||||||
// ",
|
|
||||||
// r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##,
|
|
||||||
// );
|
|
||||||
check_snippet_completion(r"
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
<|>
|
|
||||||
}
|
|
||||||
",
|
",
|
||||||
r##"[CompletionItem { label: "Test function", lookup: Some("tfn"), snippet: Some("#[test]\nfn ${1:feature}() {\n $0\n}") },
|
r#"file_id "file_id: FileId""#,
|
||||||
CompletionItem { label: "pub(crate)", lookup: None, snippet: Some("pub(crate) $0") }]"##,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ pub struct CompletionItem {
|
||||||
label: String,
|
label: String,
|
||||||
lookup: Option<String>,
|
lookup: Option<String>,
|
||||||
snippet: Option<String>,
|
snippet: Option<String>,
|
||||||
|
/// Used only internally in test, to check only specific kind of completion.
|
||||||
|
kind: CompletionKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum InsertText {
|
pub enum InsertText {
|
||||||
|
@ -13,6 +15,18 @@ pub enum InsertText {
|
||||||
Snippet { text: String },
|
Snippet { text: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub(crate) enum CompletionKind {
|
||||||
|
/// Parser-based keyword completion.
|
||||||
|
Keyword,
|
||||||
|
/// Your usual "complete all valid identifiers".
|
||||||
|
Reference,
|
||||||
|
/// "Secret sauce" completions.
|
||||||
|
Magic,
|
||||||
|
Snippet,
|
||||||
|
Unspecified,
|
||||||
|
}
|
||||||
|
|
||||||
impl CompletionItem {
|
impl CompletionItem {
|
||||||
pub(crate) fn new(label: impl Into<String>) -> Builder {
|
pub(crate) fn new(label: impl Into<String>) -> Builder {
|
||||||
let label = label.into();
|
let label = label.into();
|
||||||
|
@ -20,6 +34,7 @@ impl CompletionItem {
|
||||||
label,
|
label,
|
||||||
lookup: None,
|
lookup: None,
|
||||||
snippet: None,
|
snippet: None,
|
||||||
|
kind: CompletionKind::Unspecified,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// What user sees in pop-up in the UI.
|
/// What user sees in pop-up in the UI.
|
||||||
|
@ -50,28 +65,34 @@ pub(crate) struct Builder {
|
||||||
label: String,
|
label: String,
|
||||||
lookup: Option<String>,
|
lookup: Option<String>,
|
||||||
snippet: Option<String>,
|
snippet: Option<String>,
|
||||||
|
kind: CompletionKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Builder {
|
impl Builder {
|
||||||
pub fn add_to(self, acc: &mut Completions) {
|
pub(crate) fn add_to(self, acc: &mut Completions) {
|
||||||
acc.add(self.build())
|
acc.add(self.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> CompletionItem {
|
pub(crate) fn build(self) -> CompletionItem {
|
||||||
CompletionItem {
|
CompletionItem {
|
||||||
label: self.label,
|
label: self.label,
|
||||||
lookup: self.lookup,
|
lookup: self.lookup,
|
||||||
snippet: self.snippet,
|
snippet: self.snippet,
|
||||||
|
kind: self.kind,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
|
pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
|
||||||
self.lookup = Some(lookup.into());
|
self.lookup = Some(lookup.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn snippet(mut self, snippet: impl Into<String>) -> Builder {
|
pub(crate) fn snippet(mut self, snippet: impl Into<String>) -> Builder {
|
||||||
self.snippet = Some(snippet.into());
|
self.snippet = Some(snippet.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
pub(crate) fn kind(mut self, kind: CompletionKind) -> Builder {
|
||||||
|
self.kind = kind;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<CompletionItem> for Builder {
|
impl Into<CompletionItem> for Builder {
|
||||||
|
@ -97,6 +118,57 @@ impl Completions {
|
||||||
{
|
{
|
||||||
items.into_iter().for_each(|item| self.add(item.into()))
|
items.into_iter().for_each(|item| self.add(item.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn assert_match(&self, expected: &str, kind: CompletionKind) {
|
||||||
|
let expected = normalize(expected);
|
||||||
|
let actual = self.debug_render(kind);
|
||||||
|
test_utils::assert_eq_text!(expected.as_str(), actual.as_str(),);
|
||||||
|
|
||||||
|
/// Normalize the textual representation of `Completions`:
|
||||||
|
/// replace `;` with newlines, normalize whitespace
|
||||||
|
fn normalize(expected: &str) -> String {
|
||||||
|
use ra_syntax::{tokenize, TextUnit, TextRange, SyntaxKind::SEMI};
|
||||||
|
let mut res = String::new();
|
||||||
|
for line in expected.trim().lines() {
|
||||||
|
let line = line.trim();
|
||||||
|
let mut start_offset: TextUnit = 0.into();
|
||||||
|
// Yep, we use rust tokenize in completion tests :-)
|
||||||
|
for token in tokenize(line) {
|
||||||
|
let range = TextRange::offset_len(start_offset, token.len);
|
||||||
|
start_offset += token.len;
|
||||||
|
if token.kind == SEMI {
|
||||||
|
res.push('\n');
|
||||||
|
} else {
|
||||||
|
res.push_str(&line[range]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.push('\n');
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn debug_render(&self, kind: CompletionKind) -> String {
|
||||||
|
let mut res = String::new();
|
||||||
|
for c in self.buf.iter() {
|
||||||
|
if c.kind == kind {
|
||||||
|
if let Some(lookup) = &c.lookup {
|
||||||
|
res.push_str(lookup);
|
||||||
|
res.push_str(&format!(" {:?}", c.label));
|
||||||
|
} else {
|
||||||
|
res.push_str(&c.label);
|
||||||
|
}
|
||||||
|
if let Some(snippet) = &c.snippet {
|
||||||
|
res.push_str(&format!(" {:?}", snippet));
|
||||||
|
}
|
||||||
|
res.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Vec<CompletionItem>> for Completions {
|
impl Into<Vec<CompletionItem>> for Completions {
|
||||||
|
|
|
@ -13,7 +13,7 @@ use hir::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::RootDatabase,
|
db::RootDatabase,
|
||||||
completion::{CompletionItem, Completions},
|
completion::{CompletionItem, Completions, CompletionKind::*},
|
||||||
Cancelable
|
Cancelable
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,7 +51,11 @@ pub(super) fn completions(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.for_each(|(name, _res)| CompletionItem::new(name.to_string()).add_to(acc));
|
.for_each(|(name, _res)| {
|
||||||
|
CompletionItem::new(name.to_string())
|
||||||
|
.kind(Reference)
|
||||||
|
.add_to(acc)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
NameRefKind::Path(path) => complete_path(acc, db, module, path)?,
|
NameRefKind::Path(path) => complete_path(acc, db, module, path)?,
|
||||||
NameRefKind::BareIdentInMod => {
|
NameRefKind::BareIdentInMod => {
|
||||||
|
@ -123,9 +127,13 @@ fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Completions)
|
||||||
.scope_chain(name_ref.syntax())
|
.scope_chain(name_ref.syntax())
|
||||||
.flat_map(|scope| scopes.entries(scope).iter())
|
.flat_map(|scope| scopes.entries(scope).iter())
|
||||||
.filter(|entry| shadowed.insert(entry.name()))
|
.filter(|entry| shadowed.insert(entry.name()))
|
||||||
.for_each(|entry| CompletionItem::new(entry.name().to_string()).add_to(acc));
|
.for_each(|entry| {
|
||||||
|
CompletionItem::new(entry.name().to_string())
|
||||||
|
.kind(Reference)
|
||||||
|
.add_to(acc)
|
||||||
|
});
|
||||||
if scopes.self_param.is_some() {
|
if scopes.self_param.is_some() {
|
||||||
CompletionItem::new("self").add_to(acc);
|
CompletionItem::new("self").kind(Reference).add_to(acc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,9 +156,11 @@ fn complete_path(
|
||||||
_ => return Ok(()),
|
_ => return Ok(()),
|
||||||
};
|
};
|
||||||
let module_scope = target_module.scope(db)?;
|
let module_scope = target_module.scope(db)?;
|
||||||
module_scope
|
module_scope.entries().for_each(|(name, _res)| {
|
||||||
.entries()
|
CompletionItem::new(name.to_string())
|
||||||
.for_each(|(name, _res)| CompletionItem::new(name.to_string()).add_to(acc));
|
.kind(Reference)
|
||||||
|
.add_to(acc)
|
||||||
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,9 +174,11 @@ fn ${1:feature}() {
|
||||||
$0
|
$0
|
||||||
}",
|
}",
|
||||||
)
|
)
|
||||||
|
.kind(Snippet)
|
||||||
.add_to(acc);
|
.add_to(acc);
|
||||||
CompletionItem::new("pub(crate)")
|
CompletionItem::new("pub(crate)")
|
||||||
.snippet("pub(crate) $0")
|
.snippet("pub(crate) $0")
|
||||||
|
.kind(Snippet)
|
||||||
.add_to(acc);
|
.add_to(acc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,14 +261,362 @@ fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option<Complet
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keyword(kw: &str, snippet: &str) -> CompletionItem {
|
fn keyword(kw: &str, snippet: &str) -> CompletionItem {
|
||||||
CompletionItem::new(kw).snippet(snippet).build()
|
CompletionItem::new(kw)
|
||||||
|
.kind(Keyword)
|
||||||
|
.snippet(snippet)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete_expr_snippets(acc: &mut Completions) {
|
fn complete_expr_snippets(acc: &mut Completions) {
|
||||||
CompletionItem::new("pd")
|
CompletionItem::new("pd")
|
||||||
.snippet("eprintln!(\"$0 = {:?}\", $0);")
|
.snippet("eprintln!(\"$0 = {:?}\", $0);")
|
||||||
|
.kind(Snippet)
|
||||||
.add_to(acc);
|
.add_to(acc);
|
||||||
CompletionItem::new("ppd")
|
CompletionItem::new("ppd")
|
||||||
.snippet("eprintln!(\"$0 = {:#?}\", $0);")
|
.snippet("eprintln!(\"$0 = {:#?}\", $0);")
|
||||||
|
.kind(Snippet)
|
||||||
.add_to(acc);
|
.add_to(acc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::completion::{CompletionKind, check_completion};
|
||||||
|
|
||||||
|
fn check_reference_completion(code: &str, expected_completions: &str) {
|
||||||
|
check_completion(code, expected_completions, CompletionKind::Reference);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_keyword_completion(code: &str, expected_completions: &str) {
|
||||||
|
check_completion(code, expected_completions, CompletionKind::Keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_snippet_completion(code: &str, expected_completions: &str) {
|
||||||
|
check_completion(code, expected_completions, CompletionKind::Snippet);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_completion_let_scope() {
|
||||||
|
check_reference_completion(
|
||||||
|
r"
|
||||||
|
fn quux(x: i32) {
|
||||||
|
let y = 92;
|
||||||
|
1 + <|>;
|
||||||
|
let z = ();
|
||||||
|
}
|
||||||
|
",
|
||||||
|
"y;x;quux",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_completion_if_let_scope() {
|
||||||
|
check_reference_completion(
|
||||||
|
r"
|
||||||
|
fn quux() {
|
||||||
|
if let Some(x) = foo() {
|
||||||
|
let y = 92;
|
||||||
|
};
|
||||||
|
if let Some(a) = bar() {
|
||||||
|
let b = 62;
|
||||||
|
1 + <|>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
",
|
||||||
|
"b;a;quux",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_completion_for_scope() {
|
||||||
|
check_reference_completion(
|
||||||
|
r"
|
||||||
|
fn quux() {
|
||||||
|
for x in &[1, 2, 3] {
|
||||||
|
<|>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
",
|
||||||
|
"x;quux",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_completion_mod_scope() {
|
||||||
|
check_reference_completion(
|
||||||
|
r"
|
||||||
|
struct Foo;
|
||||||
|
enum Baz {}
|
||||||
|
fn quux() {
|
||||||
|
<|>
|
||||||
|
}
|
||||||
|
",
|
||||||
|
"quux;Foo;Baz",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_completion_mod_scope_no_self_use() {
|
||||||
|
check_reference_completion(
|
||||||
|
r"
|
||||||
|
use foo<|>;
|
||||||
|
",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_completion_self_path() {
|
||||||
|
check_reference_completion(
|
||||||
|
r"
|
||||||
|
use self::m::<|>;
|
||||||
|
|
||||||
|
mod m {
|
||||||
|
struct Bar;
|
||||||
|
}
|
||||||
|
",
|
||||||
|
"Bar",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_completion_mod_scope_nested() {
|
||||||
|
check_reference_completion(
|
||||||
|
r"
|
||||||
|
struct Foo;
|
||||||
|
mod m {
|
||||||
|
struct Bar;
|
||||||
|
fn quux() { <|> }
|
||||||
|
}
|
||||||
|
",
|
||||||
|
"quux;Bar",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_complete_type() {
|
||||||
|
check_reference_completion(
|
||||||
|
r"
|
||||||
|
struct Foo;
|
||||||
|
fn x() -> <|>
|
||||||
|
",
|
||||||
|
"Foo;x",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_complete_shadowing() {
|
||||||
|
check_reference_completion(
|
||||||
|
r"
|
||||||
|
fn foo() -> {
|
||||||
|
let bar = 92;
|
||||||
|
{
|
||||||
|
let bar = 62;
|
||||||
|
<|>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
",
|
||||||
|
"bar;foo",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_complete_self() {
|
||||||
|
check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_complete_crate_path() {
|
||||||
|
check_reference_completion(
|
||||||
|
"
|
||||||
|
//- /lib.rs
|
||||||
|
mod foo;
|
||||||
|
struct Spam;
|
||||||
|
//- /foo.rs
|
||||||
|
use crate::Sp<|>
|
||||||
|
",
|
||||||
|
"Spam;foo",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_complete_crate_path_with_braces() {
|
||||||
|
check_reference_completion(
|
||||||
|
"
|
||||||
|
//- /lib.rs
|
||||||
|
mod foo;
|
||||||
|
struct Spam;
|
||||||
|
//- /foo.rs
|
||||||
|
use crate::{Sp<|>};
|
||||||
|
",
|
||||||
|
"Spam;foo",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_complete_crate_path_in_nested_tree() {
|
||||||
|
check_reference_completion(
|
||||||
|
"
|
||||||
|
//- /lib.rs
|
||||||
|
mod foo;
|
||||||
|
pub mod bar {
|
||||||
|
pub mod baz {
|
||||||
|
pub struct Spam;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//- /foo.rs
|
||||||
|
use crate::{bar::{baz::Sp<|>}};
|
||||||
|
",
|
||||||
|
"Spam",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_completion_kewords() {
|
||||||
|
check_keyword_completion(
|
||||||
|
r"
|
||||||
|
fn quux() {
|
||||||
|
<|>
|
||||||
|
}
|
||||||
|
",
|
||||||
|
r#"
|
||||||
|
if "if $0 {}"
|
||||||
|
match "match $0 {}"
|
||||||
|
while "while $0 {}"
|
||||||
|
loop "loop {$0}"
|
||||||
|
return "return"
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_completion_else() {
|
||||||
|
check_keyword_completion(
|
||||||
|
r"
|
||||||
|
fn quux() {
|
||||||
|
if true {
|
||||||
|
()
|
||||||
|
} <|>
|
||||||
|
}
|
||||||
|
",
|
||||||
|
r#"
|
||||||
|
if "if $0 {}"
|
||||||
|
match "match $0 {}"
|
||||||
|
while "while $0 {}"
|
||||||
|
loop "loop {$0}"
|
||||||
|
else "else {$0}"
|
||||||
|
else if "else if $0 {}"
|
||||||
|
return "return"
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_completion_return_value() {
|
||||||
|
check_keyword_completion(
|
||||||
|
r"
|
||||||
|
fn quux() -> i32 {
|
||||||
|
<|>
|
||||||
|
92
|
||||||
|
}
|
||||||
|
",
|
||||||
|
r#"
|
||||||
|
if "if $0 {}"
|
||||||
|
match "match $0 {}"
|
||||||
|
while "while $0 {}"
|
||||||
|
loop "loop {$0}"
|
||||||
|
return "return $0;"
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
check_keyword_completion(
|
||||||
|
r"
|
||||||
|
fn quux() {
|
||||||
|
<|>
|
||||||
|
92
|
||||||
|
}
|
||||||
|
",
|
||||||
|
r#"
|
||||||
|
if "if $0 {}"
|
||||||
|
match "match $0 {}"
|
||||||
|
while "while $0 {}"
|
||||||
|
loop "loop {$0}"
|
||||||
|
return "return;"
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_completion_return_no_stmt() {
|
||||||
|
check_keyword_completion(
|
||||||
|
r"
|
||||||
|
fn quux() -> i32 {
|
||||||
|
match () {
|
||||||
|
() => <|>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
",
|
||||||
|
r#"
|
||||||
|
if "if $0 {}"
|
||||||
|
match "match $0 {}"
|
||||||
|
while "while $0 {}"
|
||||||
|
loop "loop {$0}"
|
||||||
|
return "return $0"
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_continue_break_completion() {
|
||||||
|
check_keyword_completion(
|
||||||
|
r"
|
||||||
|
fn quux() -> i32 {
|
||||||
|
loop { <|> }
|
||||||
|
}
|
||||||
|
",
|
||||||
|
r#"
|
||||||
|
if "if $0 {}"
|
||||||
|
match "match $0 {}"
|
||||||
|
while "while $0 {}"
|
||||||
|
loop "loop {$0}"
|
||||||
|
continue "continue"
|
||||||
|
break "break"
|
||||||
|
return "return $0"
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
check_keyword_completion(
|
||||||
|
r"
|
||||||
|
fn quux() -> i32 {
|
||||||
|
loop { || { <|> } }
|
||||||
|
}
|
||||||
|
",
|
||||||
|
r#"
|
||||||
|
if "if $0 {}"
|
||||||
|
match "match $0 {}"
|
||||||
|
while "while $0 {}"
|
||||||
|
loop "loop {$0}"
|
||||||
|
return "return $0"
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_item_snippets() {
|
||||||
|
// check_snippet_completion(r"
|
||||||
|
// <|>
|
||||||
|
// ",
|
||||||
|
// r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##,
|
||||||
|
// );
|
||||||
|
check_snippet_completion(
|
||||||
|
r"
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
<|>
|
||||||
|
}
|
||||||
|
",
|
||||||
|
r##"
|
||||||
|
tfn "Test function" "#[test]\nfn ${1:feature}() {\n $0\n}"
|
||||||
|
pub(crate) "pub(crate) $0"
|
||||||
|
"##,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -452,63 +452,3 @@ fn test_find_all_refs_for_fn_param() {
|
||||||
let refs = get_all_refs(code);
|
let refs = get_all_refs(code);
|
||||||
assert_eq!(refs.len(), 2);
|
assert_eq!(refs.len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_complete_crate_path() {
|
|
||||||
let (analysis, position) = analysis_and_position(
|
|
||||||
"
|
|
||||||
//- /lib.rs
|
|
||||||
mod foo;
|
|
||||||
struct Spam;
|
|
||||||
//- /foo.rs
|
|
||||||
use crate::Sp<|>
|
|
||||||
",
|
|
||||||
);
|
|
||||||
let completions = analysis.completions(position).unwrap().unwrap();
|
|
||||||
assert_eq_dbg(
|
|
||||||
r#"[CompletionItem { label: "Spam", lookup: None, snippet: None },
|
|
||||||
CompletionItem { label: "foo", lookup: None, snippet: None }]"#,
|
|
||||||
&completions,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_complete_crate_path_with_braces() {
|
|
||||||
let (analysis, position) = analysis_and_position(
|
|
||||||
"
|
|
||||||
//- /lib.rs
|
|
||||||
mod foo;
|
|
||||||
struct Spam;
|
|
||||||
//- /foo.rs
|
|
||||||
use crate::{Sp<|>};
|
|
||||||
",
|
|
||||||
);
|
|
||||||
let completions = analysis.completions(position).unwrap().unwrap();
|
|
||||||
assert_eq_dbg(
|
|
||||||
r#"[CompletionItem { label: "Spam", lookup: None, snippet: None },
|
|
||||||
CompletionItem { label: "foo", lookup: None, snippet: None }]"#,
|
|
||||||
&completions,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_complete_crate_path_in_nested_tree() {
|
|
||||||
let (analysis, position) = analysis_and_position(
|
|
||||||
"
|
|
||||||
//- /lib.rs
|
|
||||||
mod foo;
|
|
||||||
pub mod bar {
|
|
||||||
pub mod baz {
|
|
||||||
pub struct Spam;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//- /foo.rs
|
|
||||||
use crate::{bar::{baz::Sp<|>}};
|
|
||||||
",
|
|
||||||
);
|
|
||||||
let completions = analysis.completions(position).unwrap().unwrap();
|
|
||||||
assert_eq_dbg(
|
|
||||||
r#"[CompletionItem { label: "Spam", lookup: None, snippet: None }]"#,
|
|
||||||
&completions,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,22 +10,20 @@ pub const CURSOR_MARKER: &str = "<|>";
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! assert_eq_text {
|
macro_rules! assert_eq_text {
|
||||||
($expected:expr, $actual:expr) => {{
|
($expected:expr, $actual:expr) => {
|
||||||
let expected = $expected;
|
assert_eq_text!($expected, $actual,)
|
||||||
let actual = $actual;
|
};
|
||||||
if expected != actual {
|
|
||||||
let changeset = $crate::__Changeset::new(actual, expected, "\n");
|
|
||||||
println!("Expected:\n{}\n\nActual:\n{}\nDiff:{}\n", expected, actual, changeset);
|
|
||||||
panic!("text differs");
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
($expected:expr, $actual:expr, $($tt:tt)*) => {{
|
($expected:expr, $actual:expr, $($tt:tt)*) => {{
|
||||||
let expected = $expected;
|
let expected = $expected;
|
||||||
let actual = $actual;
|
let actual = $actual;
|
||||||
if expected != actual {
|
if expected != actual {
|
||||||
let changeset = $crate::__Changeset::new(actual, expected, "\n");
|
if expected.trim() == actual.trim() {
|
||||||
println!("Expected:\n{}\n\nActual:\n{}\n\nDiff:\n{}\n", expected, actual, changeset);
|
eprintln!("Expected:\n{:?}\n\nActual:\n{:?}\n\nWhitespace difference\n", expected, actual);
|
||||||
println!($($tt)*);
|
} else {
|
||||||
|
let changeset = $crate::__Changeset::new(actual, expected, "\n");
|
||||||
|
eprintln!("Expected:\n{}\n\nActual:\n{}\n\nDiff:\n{}\n", expected, actual, changeset);
|
||||||
|
}
|
||||||
|
eprintln!($($tt)*);
|
||||||
panic!("text differs");
|
panic!("text differs");
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
|
Loading…
Reference in a new issue