]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/tests.rs
Merge #10649
[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 proc_macros;
19 mod record;
20 mod type_pos;
21 mod use_tree;
22 mod visibility;
23 mod flyimport;
24
25 use std::mem;
26
27 use hir::{db::DefDatabase, PrefixKind, Semantics};
28 use ide_db::{
29     base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
30     helpers::{
31         insert_use::{ImportGranularity, InsertUseConfig},
32         SnippetCap,
33     },
34     RootDatabase,
35 };
36 use itertools::Itertools;
37 use stdx::{format_to, trim_indent};
38 use syntax::{AstNode, NodeOrToken, SyntaxElement};
39 use test_utils::assert_eq_text;
40
41 use crate::{CompletionConfig, CompletionItem, CompletionItemKind};
42
43 /// Lots of basic item definitions
44 const BASE_ITEMS_FIXTURE: &str = r#"
45 enum Enum { TupleV(u32), RecordV { field: u32 }, UnitV }
46 use self::Enum::TupleV;
47 mod module {}
48
49 trait Trait {}
50 static STATIC: Unit = Unit;
51 const CONST: Unit = Unit;
52 struct Record { field: u32 }
53 struct Tuple(u32);
54 struct Unit;
55 #[macro_export]
56 macro_rules! makro {}
57 #[rustc_builtin_macro]
58 pub macro Clone {}
59 fn function() {}
60 union Union { field: i32 }
61 "#;
62
63 pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
64     enable_postfix_completions: true,
65     enable_imports_on_the_fly: true,
66     enable_self_on_the_fly: true,
67     add_call_parenthesis: true,
68     add_call_argument_snippets: true,
69     snippet_cap: SnippetCap::new(true),
70     insert_use: InsertUseConfig {
71         granularity: ImportGranularity::Crate,
72         prefix_kind: PrefixKind::Plain,
73         enforce_granularity: true,
74         group: true,
75         skip_glob_imports: true,
76     },
77     snippets: Vec::new(),
78 };
79
80 pub(crate) fn completion_list(ra_fixture: &str) -> String {
81     completion_list_with_config(TEST_CONFIG, ra_fixture, true)
82 }
83
84 pub(crate) fn completion_list_no_kw(ra_fixture: &str) -> String {
85     completion_list_with_config(TEST_CONFIG, ra_fixture, false)
86 }
87
88 fn completion_list_with_config(
89     config: CompletionConfig,
90     ra_fixture: &str,
91     include_keywords: bool,
92 ) -> String {
93     // filter out all but one builtintype completion for smaller test outputs
94     let items = get_all_items(config, ra_fixture);
95     let mut bt_seen = false;
96     let items = items
97         .into_iter()
98         .filter(|it| {
99             it.kind() != CompletionItemKind::BuiltinType || !mem::replace(&mut bt_seen, true)
100         })
101         .filter(|it| include_keywords || it.kind() != CompletionItemKind::Keyword)
102         .filter(|it| include_keywords || it.kind() != CompletionItemKind::Snippet)
103         .collect();
104     render_completion_list(items)
105 }
106
107 /// Creates analysis from a multi-file fixture, returns positions marked with $0.
108 pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
109     let change_fixture = ChangeFixture::parse(ra_fixture);
110     let mut database = RootDatabase::default();
111     database.set_enable_proc_attr_macros(true);
112     database.apply_change(change_fixture.change);
113     let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
114     let offset = range_or_offset.expect_offset();
115     (database, FilePosition { file_id, offset })
116 }
117
118 pub(crate) fn do_completion(code: &str, kind: CompletionItemKind) -> Vec<CompletionItem> {
119     do_completion_with_config(TEST_CONFIG, code, kind)
120 }
121
122 pub(crate) fn do_completion_with_config(
123     config: CompletionConfig,
124     code: &str,
125     kind: CompletionItemKind,
126 ) -> Vec<CompletionItem> {
127     get_all_items(config, code)
128         .into_iter()
129         .filter(|c| c.kind() == kind)
130         .sorted_by(|l, r| l.label().cmp(r.label()))
131         .collect()
132 }
133
134 fn render_completion_list(completions: Vec<CompletionItem>) -> String {
135     fn monospace_width(s: &str) -> usize {
136         s.chars().count()
137     }
138     let label_width =
139         completions.iter().map(|it| monospace_width(it.label())).max().unwrap_or_default().min(22);
140     completions
141         .into_iter()
142         .map(|it| {
143             let tag = it.kind().tag();
144             let var_name = format!("{} {}", tag, it.label());
145             let mut buf = var_name;
146             if let Some(detail) = it.detail() {
147                 let width = label_width.saturating_sub(monospace_width(it.label()));
148                 format_to!(buf, "{:width$} {}", "", detail, width = width);
149             }
150             if it.deprecated() {
151                 format_to!(buf, " DEPRECATED");
152             }
153             format_to!(buf, "\n");
154             buf
155         })
156         .collect()
157 }
158
159 pub(crate) fn check_edit(what: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
160     check_edit_with_config(TEST_CONFIG, what, ra_fixture_before, ra_fixture_after)
161 }
162
163 pub(crate) fn check_edit_with_config(
164     config: CompletionConfig,
165     what: &str,
166     ra_fixture_before: &str,
167     ra_fixture_after: &str,
168 ) {
169     let ra_fixture_after = trim_indent(ra_fixture_after);
170     let (db, position) = position(ra_fixture_before);
171     let completions: Vec<CompletionItem> =
172         crate::completions(&db, &config, position).unwrap().into();
173     let (completion,) = completions
174         .iter()
175         .filter(|it| it.lookup() == what)
176         .collect_tuple()
177         .unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions));
178     let mut actual = db.file_text(position.file_id).to_string();
179
180     let mut combined_edit = completion.text_edit().to_owned();
181     completion
182         .imports_to_add()
183         .iter()
184         .filter_map(|edit| edit.to_text_edit(config.insert_use))
185         .for_each(|text_edit| {
186             combined_edit.union(text_edit).expect(
187                 "Failed to apply completion resolve changes: change ranges overlap, but should not",
188             )
189         });
190
191     combined_edit.apply(&mut actual);
192     assert_eq_text!(&ra_fixture_after, &actual)
193 }
194
195 pub(crate) fn check_pattern_is_applicable(code: &str, check: impl FnOnce(SyntaxElement) -> bool) {
196     let (db, pos) = position(code);
197
198     let sema = Semantics::new(&db);
199     let original_file = sema.parse(pos.file_id);
200     let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
201     assert!(check(NodeOrToken::Token(token)));
202 }
203
204 pub(crate) fn check_pattern_is_not_applicable(code: &str, check: fn(SyntaxElement) -> bool) {
205     let (db, pos) = position(code);
206     let sema = Semantics::new(&db);
207     let original_file = sema.parse(pos.file_id);
208     let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
209     assert!(!check(NodeOrToken::Token(token)));
210 }
211
212 pub(crate) fn get_all_items(config: CompletionConfig, code: &str) -> Vec<CompletionItem> {
213     let (db, position) = position(code);
214     crate::completions(&db, &config, position).map_or_else(Vec::default, Into::into)
215 }
216
217 fn check_no_completion(ra_fixture: &str) {
218     let (db, position) = position(ra_fixture);
219
220     assert!(
221         crate::completions(&db, &TEST_CONFIG, position).is_none(),
222         "Completions were generated, but weren't expected"
223     );
224 }
225
226 #[test]
227 fn test_no_completions_required() {
228     cov_mark::check!(no_completion_required);
229     check_no_completion(r#"fn foo() { for i i$0 }"#);
230 }
231
232 #[test]
233 fn regression_10042() {
234     completion_list(
235         r#"
236 macro_rules! preset {
237     ($($x:ident)&&*) => {
238         {
239             let mut v = Vec::new();
240             $(
241                 v.push($x.into());
242             )*
243             v
244         }
245     };
246 }
247
248 fn foo() {
249     preset!(foo$0);
250 }
251 "#,
252     );
253 }
254
255 #[test]
256 fn no_completions_in_comments() {
257     cov_mark::check!(no_keyword_completion_in_comments);
258     assert_eq!(
259         completion_list(
260             r#"
261 fn test() {
262 let x = 2; // A comment$0
263 }
264 "#,
265         ),
266         String::new(),
267     );
268     assert_eq!(
269         completion_list(
270             r#"
271 /*
272 Some multi-line comment$0
273 */
274 "#,
275         ),
276         String::new(),
277     );
278     assert_eq!(
279         completion_list(
280             r#"
281 /// Some doc comment
282 /// let test$0 = 1
283 "#,
284         ),
285         String::new(),
286     );
287 }