From 45232dfa689bafadf98b92ef30fd32ea9a5e9e7a Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 21 Dec 2018 18:13:21 +0300 Subject: organize completion tests better --- .../ra_analysis/src/completion/completion_item.rs | 80 ++++- .../src/completion/reference_completion.rs | 376 ++++++++++++++++++++- 2 files changed, 444 insertions(+), 12 deletions(-) (limited to 'crates/ra_analysis/src/completion') diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs index 8aa9da005..d5d751759 100644 --- a/crates/ra_analysis/src/completion/completion_item.rs +++ b/crates/ra_analysis/src/completion/completion_item.rs @@ -6,6 +6,8 @@ pub struct CompletionItem { label: String, lookup: Option, snippet: Option, + /// Used only internally in test, to check only specific kind of completion. + kind: CompletionKind, } pub enum InsertText { @@ -13,6 +15,18 @@ pub enum InsertText { 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 { pub(crate) fn new(label: impl Into) -> Builder { let label = label.into(); @@ -20,6 +34,7 @@ impl CompletionItem { label, lookup: None, snippet: None, + kind: CompletionKind::Unspecified, } } /// What user sees in pop-up in the UI. @@ -50,28 +65,34 @@ pub(crate) struct Builder { label: String, lookup: Option, snippet: Option, + kind: CompletionKind, } impl Builder { - pub fn add_to(self, acc: &mut Completions) { + pub(crate) fn add_to(self, acc: &mut Completions) { acc.add(self.build()) } - pub fn build(self) -> CompletionItem { + pub(crate) fn build(self) -> CompletionItem { CompletionItem { label: self.label, lookup: self.lookup, snippet: self.snippet, + kind: self.kind, } } - pub fn lookup_by(mut self, lookup: impl Into) -> Builder { + pub(crate) fn lookup_by(mut self, lookup: impl Into) -> Builder { self.lookup = Some(lookup.into()); self } - pub fn snippet(mut self, snippet: impl Into) -> Builder { + pub(crate) fn snippet(mut self, snippet: impl Into) -> Builder { self.snippet = Some(snippet.into()); self } + pub(crate) fn kind(mut self, kind: CompletionKind) -> Builder { + self.kind = kind; + self + } } impl Into for Builder { @@ -97,6 +118,57 @@ impl Completions { { 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> for Completions { diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs index c578e9e8b..c2a650b6d 100644 --- a/crates/ra_analysis/src/completion/reference_completion.rs +++ b/crates/ra_analysis/src/completion/reference_completion.rs @@ -13,7 +13,7 @@ use hir::{ use crate::{ db::RootDatabase, - completion::{CompletionItem, Completions}, + completion::{CompletionItem, Completions, CompletionKind::*}, 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::BareIdentInMod => { @@ -123,9 +127,13 @@ fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Completions) .scope_chain(name_ref.syntax()) .flat_map(|scope| scopes.entries(scope).iter()) .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() { - CompletionItem::new("self").add_to(acc); + CompletionItem::new("self").kind(Reference).add_to(acc); } } @@ -148,9 +156,11 @@ fn complete_path( _ => return Ok(()), }; let module_scope = target_module.scope(db)?; - module_scope - .entries() - .for_each(|(name, _res)| CompletionItem::new(name.to_string()).add_to(acc)); + module_scope.entries().for_each(|(name, _res)| { + CompletionItem::new(name.to_string()) + .kind(Reference) + .add_to(acc) + }); Ok(()) } @@ -164,9 +174,11 @@ fn ${1:feature}() { $0 }", ) + .kind(Snippet) .add_to(acc); CompletionItem::new("pub(crate)") .snippet("pub(crate) $0") + .kind(Snippet) .add_to(acc); } @@ -249,14 +261,362 @@ fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option CompletionItem { - CompletionItem::new(kw).snippet(snippet).build() + CompletionItem::new(kw) + .kind(Keyword) + .snippet(snippet) + .build() } fn complete_expr_snippets(acc: &mut Completions) { CompletionItem::new("pd") .snippet("eprintln!(\"$0 = {:?}\", $0);") + .kind(Snippet) .add_to(acc); CompletionItem::new("ppd") .snippet("eprintln!(\"$0 = {:#?}\", $0);") + .kind(Snippet) .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" + "##, + ); + } + +} -- cgit v1.2.3