]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/tests.rs
fix: Do not show functional update completion when already qualified
[rust.git] / crates / ide_completion / src / tests.rs
1 //! Tests and test utilities for completions.
2 //!
3 //! Most tests live in this module or its submodules. The tests in these submodules are "location"
4 //! oriented, that is they try to check completions for something like type position, param position
5 //! etc.
6 //! Tests that are more orientated towards specific completion types like visibility checks of path
7 //! completions or `check_edit` tests usually live in their respective completion modules instead.
8 //! This gives this test module and its submodules here the main purpose of giving the developer an
9 //! overview of whats being completed where, not how.
10
11 mod attribute;
12 mod expression;
13 mod fn_param;
14 mod item_list;
15 mod item;
16 mod pattern;
17 mod predicate;
18 mod record;
19 mod sourcegen;
20 mod type_pos;
21 mod use_tree;
22 mod visibility;
23
24 use std::mem;
25
26 use hir::{PrefixKind, Semantics};
27 use ide_db::{
28     base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
29     helpers::{
30         insert_use::{ImportGranularity, InsertUseConfig},
31         SnippetCap,
32     },
33     RootDatabase,
34 };
35 use itertools::Itertools;
36 use stdx::{format_to, trim_indent};
37 use syntax::{AstNode, NodeOrToken, SyntaxElement};
38 use test_utils::assert_eq_text;
39
40 use crate::{item::CompletionKind, CompletionConfig, CompletionItem};
41
42 /// Lots of basic item definitions
43 const BASE_ITEMS_FIXTURE: &str = r#"
44 enum Enum { TupleV(u32), RecordV { field: u32 }, UnitV }
45 use self::Enum::TupleV;
46 mod module {}
47
48 trait Trait {}
49 static STATIC: Unit = Unit;
50 const CONST: Unit = Unit;
51 struct Record { field: u32 }
52 struct Tuple(u32);
53 struct Unit;
54 #[macro_export]
55 macro_rules! makro {}
56 #[rustc_builtin_macro]
57 pub macro Clone {}
58 fn function() {}
59 union Union { field: i32 }
60 "#;
61
62 pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
63     enable_postfix_completions: true,
64     enable_imports_on_the_fly: true,
65     enable_self_on_the_fly: true,
66     add_call_parenthesis: true,
67     add_call_argument_snippets: true,
68     snippet_cap: SnippetCap::new(true),
69     insert_use: InsertUseConfig {
70         granularity: ImportGranularity::Crate,
71         prefix_kind: PrefixKind::Plain,
72         enforce_granularity: true,
73         group: true,
74         skip_glob_imports: true,
75     },
76 };
77
78 pub(crate) fn completion_list(code: &str) -> String {
79     completion_list_with_config(TEST_CONFIG, code)
80 }
81
82 fn completion_list_with_config(config: CompletionConfig, code: &str) -> String {
83     // filter out all but one builtintype completion for smaller test outputs
84     let items = get_all_items(config, code);
85     let mut bt_seen = false;
86     let items = items
87         .into_iter()
88         .filter(|it| {
89             it.completion_kind != CompletionKind::BuiltinType || !mem::replace(&mut bt_seen, true)
90         })
91         .collect();
92     render_completion_list(items)
93 }
94
95 /// Creates analysis from a multi-file fixture, returns positions marked with $0.
96 pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
97     let change_fixture = ChangeFixture::parse(ra_fixture);
98     let mut database = RootDatabase::default();
99     database.apply_change(change_fixture.change);
100     let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
101     let offset = range_or_offset.expect_offset();
102     (database, FilePosition { file_id, offset })
103 }
104
105 pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
106     do_completion_with_config(TEST_CONFIG, code, kind)
107 }
108
109 pub(crate) fn do_completion_with_config(
110     config: CompletionConfig,
111     code: &str,
112     kind: CompletionKind,
113 ) -> Vec<CompletionItem> {
114     get_all_items(config, code)
115         .into_iter()
116         .filter(|c| c.completion_kind == kind)
117         .sorted_by(|l, r| l.label().cmp(r.label()))
118         .collect()
119 }
120
121 pub(crate) fn filtered_completion_list(code: &str, kind: CompletionKind) -> String {
122     filtered_completion_list_with_config(TEST_CONFIG, code, kind)
123 }
124
125 pub(crate) fn filtered_completion_list_with_config(
126     config: CompletionConfig,
127     code: &str,
128     kind: CompletionKind,
129 ) -> String {
130     let kind_completions: Vec<CompletionItem> =
131         get_all_items(config, code).into_iter().filter(|c| c.completion_kind == kind).collect();
132     render_completion_list(kind_completions)
133 }
134
135 fn render_completion_list(completions: Vec<CompletionItem>) -> String {
136     fn monospace_width(s: &str) -> usize {
137         s.chars().count()
138     }
139     let label_width =
140         completions.iter().map(|it| monospace_width(it.label())).max().unwrap_or_default().min(22);
141     completions
142         .into_iter()
143         .map(|it| {
144             let tag = it.kind().unwrap().tag();
145             let var_name = format!("{} {}", tag, it.label());
146             let mut buf = var_name;
147             if let Some(detail) = it.detail() {
148                 let width = label_width.saturating_sub(monospace_width(it.label()));
149                 format_to!(buf, "{:width$} {}", "", detail, width = width);
150             }
151             if it.deprecated() {
152                 format_to!(buf, " DEPRECATED");
153             }
154             format_to!(buf, "\n");
155             buf
156         })
157         .collect()
158 }
159
160 pub(crate) fn check_edit(what: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
161     check_edit_with_config(TEST_CONFIG, what, ra_fixture_before, ra_fixture_after)
162 }
163
164 pub(crate) fn check_edit_with_config(
165     config: CompletionConfig,
166     what: &str,
167     ra_fixture_before: &str,
168     ra_fixture_after: &str,
169 ) {
170     let ra_fixture_after = trim_indent(ra_fixture_after);
171     let (db, position) = position(ra_fixture_before);
172     let completions: Vec<CompletionItem> =
173         crate::completions(&db, &config, position).unwrap().into();
174     let (completion,) = completions
175         .iter()
176         .filter(|it| it.lookup() == what)
177         .collect_tuple()
178         .unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions));
179     let mut actual = db.file_text(position.file_id).to_string();
180
181     let mut combined_edit = completion.text_edit().to_owned();
182     if let Some(import_text_edit) =
183         completion.import_to_add().and_then(|edit| edit.to_text_edit(config.insert_use))
184     {
185         combined_edit.union(import_text_edit).expect(
186             "Failed to apply completion resolve changes: change ranges overlap, but should not",
187         )
188     }
189
190     combined_edit.apply(&mut actual);
191     assert_eq_text!(&ra_fixture_after, &actual)
192 }
193
194 pub(crate) fn check_pattern_is_applicable(code: &str, check: impl FnOnce(SyntaxElement) -> bool) {
195     let (db, pos) = position(code);
196
197     let sema = Semantics::new(&db);
198     let original_file = sema.parse(pos.file_id);
199     let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
200     assert!(check(NodeOrToken::Token(token)));
201 }
202
203 pub(crate) fn check_pattern_is_not_applicable(code: &str, check: fn(SyntaxElement) -> bool) {
204     let (db, pos) = position(code);
205     let sema = Semantics::new(&db);
206     let original_file = sema.parse(pos.file_id);
207     let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
208     assert!(!check(NodeOrToken::Token(token)));
209 }
210
211 pub(crate) fn get_all_items(config: CompletionConfig, code: &str) -> Vec<CompletionItem> {
212     let (db, position) = position(code);
213     crate::completions(&db, &config, position).map_or_else(Vec::default, Into::into)
214 }
215
216 fn check_no_completion(ra_fixture: &str) {
217     let (db, position) = position(ra_fixture);
218
219     assert!(
220         crate::completions(&db, &TEST_CONFIG, position).is_none(),
221         "Completions were generated, but weren't expected"
222     );
223 }
224
225 #[test]
226 fn test_no_completions_required() {
227     cov_mark::check!(no_completion_required);
228     check_no_completion(r#"fn foo() { for i i$0 }"#);
229 }