From d338a803941c2b0ac83decfcdfac33c09dfaa971 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 16 Jun 2021 17:37:23 +0200 Subject: Start refactoring ide_completion tests --- crates/ide_completion/src/completions.rs | 113 +++++++------- crates/ide_completion/src/completions/keyword.rs | 36 ----- crates/ide_completion/src/completions/pattern.rs | 2 +- .../src/completions/qualified_path.rs | 18 --- crates/ide_completion/src/completions/snippet.rs | 17 -- .../src/completions/unqualified_path.rs | 2 +- crates/ide_completion/src/lib.rs | 125 +-------------- crates/ide_completion/src/tests.rs | 64 ++++++++ crates/ide_completion/src/tests/item_list.rs | 172 +++++++++++++++++++++ 9 files changed, 302 insertions(+), 247 deletions(-) create mode 100644 crates/ide_completion/src/tests.rs create mode 100644 crates/ide_completion/src/tests/item_list.rs (limited to 'crates') diff --git a/crates/ide_completion/src/completions.rs b/crates/ide_completion/src/completions.rs index 783305005..cba5eb0c6 100644 --- a/crates/ide_completion/src/completions.rs +++ b/crates/ide_completion/src/completions.rs @@ -41,9 +41,9 @@ pub struct Completions { buf: Vec, } -impl Into> for Completions { - fn into(self) -> Vec { - self.buf +impl From for Vec { + fn from(val: Completions) -> Self { + val.buf } } @@ -74,35 +74,6 @@ impl Completions { items.into_iter().for_each(|item| self.add(item.into())) } - pub(crate) fn add_field( - &mut self, - ctx: &CompletionContext, - receiver: Option, - field: hir::Field, - ty: &hir::Type, - ) { - let item = render_field(RenderContext::new(ctx), receiver, field, ty); - self.add(item); - } - - pub(crate) fn add_tuple_field( - &mut self, - ctx: &CompletionContext, - receiver: Option, - field: usize, - ty: &hir::Type, - ) { - let item = render_tuple_field(RenderContext::new(ctx), receiver, field, ty); - self.add(item); - } - - pub(crate) fn add_static_lifetime(&mut self, ctx: &CompletionContext) { - let mut item = - CompletionItem::new(CompletionKind::Reference, ctx.source_range(), "'static"); - item.kind(CompletionItemKind::SymbolKind(SymbolKind::LifetimeParam)); - self.add(item.build()); - } - pub(crate) fn add_resolution( &mut self, ctx: &CompletionContext, @@ -144,72 +115,102 @@ impl Completions { self.add_opt(render_method(RenderContext::new(ctx), None, receiver, local_name, func)); } - pub(crate) fn add_variant_pat( + pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { + self.add_opt(render_const(RenderContext::new(ctx), constant)); + } + + pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) { + self.add_opt(render_type_alias(RenderContext::new(ctx), type_alias)); + } + + pub(crate) fn add_type_alias_with_eq( &mut self, ctx: &CompletionContext, - variant: hir::Variant, - local_name: Option, + type_alias: hir::TypeAlias, ) { - self.add_opt(render_variant_pat(RenderContext::new(ctx), variant, local_name, None)); + self.add_opt(render_type_alias_with_eq(RenderContext::new(ctx), type_alias)); } - pub(crate) fn add_qualified_variant_pat( + pub(crate) fn add_qualified_enum_variant( &mut self, ctx: &CompletionContext, variant: hir::Variant, path: hir::ModPath, ) { - self.add_opt(render_variant_pat(RenderContext::new(ctx), variant, None, Some(path))); + let item = render_variant(RenderContext::new(ctx), None, None, variant, Some(path)); + self.add(item); } - pub(crate) fn add_struct_pat( + pub(crate) fn add_enum_variant( &mut self, ctx: &CompletionContext, - strukt: hir::Struct, + variant: hir::Variant, local_name: Option, ) { - self.add_opt(render_struct_pat(RenderContext::new(ctx), strukt, local_name)); + let item = render_variant(RenderContext::new(ctx), None, local_name, variant, None); + self.add(item); } - pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { - self.add_opt(render_const(RenderContext::new(ctx), constant)); + pub(crate) fn add_field( + &mut self, + ctx: &CompletionContext, + receiver: Option, + field: hir::Field, + ty: &hir::Type, + ) { + let item = render_field(RenderContext::new(ctx), receiver, field, ty); + self.add(item); } - pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) { - self.add_opt(render_type_alias(RenderContext::new(ctx), type_alias)); + pub(crate) fn add_tuple_field( + &mut self, + ctx: &CompletionContext, + receiver: Option, + field: usize, + ty: &hir::Type, + ) { + let item = render_tuple_field(RenderContext::new(ctx), receiver, field, ty); + self.add(item); } - pub(crate) fn add_type_alias_with_eq( + pub(crate) fn add_static_lifetime(&mut self, ctx: &CompletionContext) { + let mut item = + CompletionItem::new(CompletionKind::Reference, ctx.source_range(), "'static"); + item.kind(CompletionItemKind::SymbolKind(SymbolKind::LifetimeParam)); + self.add(item.build()); + } + + pub(crate) fn add_variant_pat( &mut self, ctx: &CompletionContext, - type_alias: hir::TypeAlias, + variant: hir::Variant, + local_name: Option, ) { - self.add_opt(render_type_alias_with_eq(RenderContext::new(ctx), type_alias)); + self.add_opt(render_variant_pat(RenderContext::new(ctx), variant, local_name, None)); } - pub(crate) fn add_qualified_enum_variant( + pub(crate) fn add_qualified_variant_pat( &mut self, ctx: &CompletionContext, variant: hir::Variant, path: hir::ModPath, ) { - let item = render_variant(RenderContext::new(ctx), None, None, variant, Some(path)); - self.add(item); + self.add_opt(render_variant_pat(RenderContext::new(ctx), variant, None, Some(path))); } - pub(crate) fn add_enum_variant( + pub(crate) fn add_struct_pat( &mut self, ctx: &CompletionContext, - variant: hir::Variant, + strukt: hir::Struct, local_name: Option, ) { - let item = render_variant(RenderContext::new(ctx), None, local_name, variant, None); - self.add(item); + self.add_opt(render_struct_pat(RenderContext::new(ctx), strukt, local_name)); } } /// Calls the callback for each variant of the provided enum with the path to the variant. -fn complete_enum_variants( +/// Skips variants that are visible with single segment paths. +fn enum_variants_with_paths( acc: &mut Completions, ctx: &CompletionContext, enum_: hir::Enum, diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs index 0fccbeccf..2c42438d6 100644 --- a/crates/ide_completion/src/completions/keyword.rs +++ b/crates/ide_completion/src/completions/keyword.rs @@ -230,30 +230,6 @@ mod tests { ); } - #[test] - fn test_keywords_at_source_file_level() { - check( - r"m$0", - expect![[r#" - kw pub(crate) - kw pub - kw unsafe - kw fn - kw const - kw type - kw use - kw impl - kw trait - kw static - kw extern - kw mod - kw enum - kw struct - kw union - "#]], - ); - } - #[test] fn test_keywords_in_function() { check( @@ -442,18 +418,6 @@ fn quux() -> i32 { ); } - #[test] - fn test_keywords_after_unsafe_in_item_list() { - check( - r"unsafe $0", - expect![[r#" - kw fn - kw trait - kw impl - "#]], - ); - } - #[test] fn test_keywords_after_unsafe_in_block_expr() { check( diff --git a/crates/ide_completion/src/completions/pattern.rs b/crates/ide_completion/src/completions/pattern.rs index 1daa8595a..e45b2a1ea 100644 --- a/crates/ide_completion/src/completions/pattern.rs +++ b/crates/ide_completion/src/completions/pattern.rs @@ -13,7 +13,7 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { if let Some(hir::Adt::Enum(e)) = ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt()) { - super::complete_enum_variants(acc, ctx, e, |acc, ctx, variant, path| { + super::enum_variants_with_paths(acc, ctx, e, |acc, ctx, variant, path| { acc.add_qualified_variant_pat(ctx, variant, path.clone()); acc.add_qualified_enum_variant(ctx, variant, path); }); diff --git a/crates/ide_completion/src/completions/qualified_path.rs b/crates/ide_completion/src/completions/qualified_path.rs index 1643eeed4..165b9e6a5 100644 --- a/crates/ide_completion/src/completions/qualified_path.rs +++ b/crates/ide_completion/src/completions/qualified_path.rs @@ -713,24 +713,6 @@ impl MyStruct { ); } - #[test] - fn completes_in_item_list() { - check( - r#" -struct MyStruct {} -#[macro_export] -macro_rules! foo {} -mod bar {} - -crate::$0 -"#, - expect![[r#" - md bar - ma foo!(…) #[macro_export] macro_rules! foo - "#]], - ) - } - #[test] fn test_super_super_completion() { check( diff --git a/crates/ide_completion/src/completions/snippet.rs b/crates/ide_completion/src/completions/snippet.rs index b9862de67..5560f1acf 100644 --- a/crates/ide_completion/src/completions/snippet.rs +++ b/crates/ide_completion/src/completions/snippet.rs @@ -105,21 +105,4 @@ mod tests { check(r#"fn foo(x: i32) { ::foo$0 }"#, expect![[""]]); check(r#"fn foo(x: i32) { ::$0 }"#, expect![[""]]); } - - #[test] - fn completes_snippets_in_items() { - check( - r#" -#[cfg(test)] -mod tests { - $0 -} -"#, - expect![[r#" - sn tmod (Test module) - sn tfn (Test function) - sn macro_rules - "#]], - ) - } } diff --git a/crates/ide_completion/src/completions/unqualified_path.rs b/crates/ide_completion/src/completions/unqualified_path.rs index b5af1c810..2c623bf7a 100644 --- a/crates/ide_completion/src/completions/unqualified_path.rs +++ b/crates/ide_completion/src/completions/unqualified_path.rs @@ -40,7 +40,7 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC if let Some(hir::Adt::Enum(e)) = ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt()) { - super::complete_enum_variants(acc, ctx, e, |acc, ctx, variant, path| { + super::enum_variants_with_paths(acc, ctx, e, |acc, ctx, variant, path| { acc.add_qualified_enum_variant(ctx, variant, path) }); } diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index 18983aa01..be6442426 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs @@ -1,14 +1,16 @@ //! `completions` crate provides utilities for generating completions of user input. +mod completions; mod config; -mod item; mod context; +mod item; mod patterns; -#[cfg(test)] -mod test_utils; mod render; -mod completions; +#[cfg(test)] +mod tests; +#[cfg(test)] +mod test_utils; use completions::flyimport::position_for_import; use ide_db::{ @@ -141,6 +143,7 @@ pub fn completions( let ctx = CompletionContext::new(db, position, config)?; if ctx.no_completion_required() { + cov_mark::hit!(no_completion_required); // No work required here. return None; } @@ -200,117 +203,3 @@ pub fn resolve_completion_edits( ImportEdit { import, scope }.to_text_edit(config.insert_use).map(|edit| vec![edit]) } - -#[cfg(test)] -mod tests { - use crate::test_utils::{self, TEST_CONFIG}; - - struct DetailAndDocumentation<'a> { - detail: &'a str, - documentation: &'a str, - } - - fn check_detail_and_documentation(ra_fixture: &str, expected: DetailAndDocumentation) { - let (db, position) = test_utils::position(ra_fixture); - let config = TEST_CONFIG; - let completions: Vec<_> = crate::completions(&db, &config, position).unwrap().into(); - for item in completions { - if item.detail() == Some(expected.detail) { - let opt = item.documentation(); - let doc = opt.as_ref().map(|it| it.as_str()); - assert_eq!(doc, Some(expected.documentation)); - return; - } - } - panic!("completion detail not found: {}", expected.detail) - } - - fn check_no_completion(ra_fixture: &str) { - let (db, position) = test_utils::position(ra_fixture); - let config = TEST_CONFIG; - - let completions: Option> = crate::completions(&db, &config, position) - .and_then(|completions| { - let completions: Vec<_> = completions.into(); - if completions.is_empty() { - None - } else { - Some(completions) - } - }) - .map(|completions| { - completions.into_iter().map(|completion| format!("{:?}", completion)).collect() - }); - - // `assert_eq` instead of `assert!(completions.is_none())` to get the list of completions if test will panic. - assert_eq!(completions, None, "Completions were generated, but weren't expected"); - } - - #[test] - fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() { - check_detail_and_documentation( - r#" -macro_rules! bar { - () => { - struct Bar; - impl Bar { - #[doc = "Do the foo"] - fn foo(&self) {} - } - } -} - -bar!(); - -fn foo() { - let bar = Bar; - bar.fo$0; -} -"#, - DetailAndDocumentation { detail: "fn(&self)", documentation: "Do the foo" }, - ); - } - - #[test] - fn test_completion_detail_from_macro_generated_struct_fn_doc_comment() { - check_detail_and_documentation( - r#" -macro_rules! bar { - () => { - struct Bar; - impl Bar { - /// Do the foo - fn foo(&self) {} - } - } -} - -bar!(); - -fn foo() { - let bar = Bar; - bar.fo$0; -} -"#, - DetailAndDocumentation { detail: "fn(&self)", documentation: "Do the foo" }, - ); - } - - #[test] - fn test_no_completions_required() { - // There must be no hint for 'in' keyword. - check_no_completion(r#"fn foo() { for i i$0 }"#); - // After 'in' keyword hints may be spawned. - check_detail_and_documentation( - r#" -/// Do the foo -fn foo() -> &'static str { "foo" } - -fn bar() { - for c in fo$0 -} -"#, - DetailAndDocumentation { detail: "fn() -> &str", documentation: "Do the foo" }, - ); - } -} diff --git a/crates/ide_completion/src/tests.rs b/crates/ide_completion/src/tests.rs new file mode 100644 index 000000000..4485a908e --- /dev/null +++ b/crates/ide_completion/src/tests.rs @@ -0,0 +1,64 @@ +mod item_list; + +use expect_test::Expect; +use stdx::format_to; + +use crate::{ + test_utils::{self, get_all_items, TEST_CONFIG}, + CompletionConfig, CompletionItem, +}; + +fn completion_list(code: &str) -> String { + completion_list_with_config(TEST_CONFIG, code) +} + +fn completion_list_with_config(config: CompletionConfig, code: &str) -> String { + fn monospace_width(s: &str) -> usize { + s.chars().count() + } + + let kind_completions: Vec = get_all_items(config, code).into_iter().collect(); + let label_width = kind_completions + .iter() + .map(|it| monospace_width(it.label())) + .max() + .unwrap_or_default() + .min(16); + kind_completions + .into_iter() + .map(|it| { + let tag = it.kind().unwrap().tag(); + let var_name = format!("{} {}", tag, it.label()); + let mut buf = var_name; + if let Some(detail) = it.detail() { + let width = label_width.saturating_sub(monospace_width(it.label())); + format_to!(buf, "{:width$} {}", "", detail, width = width); + } + if it.deprecated() { + format_to!(buf, " DEPRECATED"); + } + format_to!(buf, "\n"); + buf + }) + .collect() +} + +fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture); + expect.assert_eq(&actual) +} + +fn check_no_completion(ra_fixture: &str) { + let (db, position) = test_utils::position(ra_fixture); + + assert!( + crate::completions(&db, &TEST_CONFIG, position).is_none(), + "Completions were generated, but weren't expected" + ); +} + +#[test] +fn test_no_completions_required() { + cov_mark::check!(no_completion_required); + check_no_completion(r#"fn foo() { for i i$0 }"#); +} diff --git a/crates/ide_completion/src/tests/item_list.rs b/crates/ide_completion/src/tests/item_list.rs new file mode 100644 index 000000000..bd060a632 --- /dev/null +++ b/crates/ide_completion/src/tests/item_list.rs @@ -0,0 +1,172 @@ +use expect_test::expect; + +use crate::tests::check; + +#[test] +fn in_mod_item_list() { + check( + r#"mod tests { + $0 +} +"#, + expect![[r#" + kw pub(crate) + kw pub + kw unsafe + kw fn + kw const + kw type + kw use + kw impl + kw trait + kw static + kw extern + kw mod + kw enum + kw struct + kw union + sn tmod (Test module) + sn tfn (Test function) + sn macro_rules + "#]], + ) +} + +#[test] +fn in_source_file_item_list() { + check( + r#" +enum Enum { Variant } +struct MyStruct {} +#[macro_export] +macro_rules! foo {} +mod bar {} +const CONST: () = (); + +$0"#, + expect![[r##" + kw pub(crate) + kw pub + kw unsafe + kw fn + kw const + kw type + kw use + kw impl + kw trait + kw static + kw extern + kw mod + kw enum + kw struct + kw union + sn tmod (Test module) + sn tfn (Test function) + sn macro_rules + md bar + ma foo!(…) #[macro_export] macro_rules! foo + ma foo!(…) #[macro_export] macro_rules! foo + "##]], + ) +} + +#[test] +fn in_qualified_path() { + check( + r#" +enum Enum { Variant } +struct MyStruct {} +#[macro_export] +macro_rules! foo {} +mod bar {} +const CONST: () = (); + +crate::$0"#, + expect![[r##" + kw pub(crate) + kw pub + kw unsafe + kw fn + kw const + kw type + kw use + kw impl + kw trait + kw static + kw extern + kw mod + kw enum + kw struct + kw union + sn tmod (Test module) + sn tfn (Test function) + sn macro_rules + md bar + ma foo!(…) #[macro_export] macro_rules! foo + "##]], + ) +} + +#[test] +fn after_unsafe_token() { + check( + r#" +enum Enum { Variant } +struct MyStruct {} +#[macro_export] +macro_rules! foo {} +mod bar {} +const CONST: () = (); + +unsafe $0"#, + expect![[r##" + kw fn + kw trait + kw impl + sn tmod (Test module) + sn tfn (Test function) + sn macro_rules + md bar + ma foo!(…) #[macro_export] macro_rules! foo + ma foo!(…) #[macro_export] macro_rules! foo + "##]], + ); +} + +#[test] +fn after_visibility() { + check( + r#" +enum Enum { Variant } +struct MyStruct {} +#[macro_export] +macro_rules! foo {} +mod bar {} +const CONST: () = (); + +pub $0"#, + expect![[r##" + kw pub(crate) + kw pub + kw unsafe + kw fn + kw const + kw type + kw use + kw impl + kw trait + kw static + kw extern + kw mod + kw enum + kw struct + kw union + sn tmod (Test module) + sn tfn (Test function) + sn macro_rules + md bar + ma foo!(…) #[macro_export] macro_rules! foo + ma foo!(…) #[macro_export] macro_rules! foo + "##]], + ); +} -- cgit v1.2.3