• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 flyimport;
14 mod fn_param;
15 mod item_list;
16 mod item;
17 mod pattern;
18 mod predicate;
19 mod proc_macros;
20 mod record;
21 mod special;
22 mod type_pos;
23 mod use_tree;
24 mod visibility;
25 
26 use expect_test::Expect;
27 use hir::PrefixKind;
28 use ide_db::{
29     base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
30     imports::insert_use::{ImportGranularity, InsertUseConfig},
31     RootDatabase, SnippetCap,
32 };
33 use itertools::Itertools;
34 use stdx::{format_to, trim_indent};
35 use test_utils::assert_eq_text;
36 
37 use crate::{
38     resolve_completion_edits, CallableSnippets, CompletionConfig, CompletionItem,
39     CompletionItemKind,
40 };
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     enable_private_editable: false,
67     callable: Some(CallableSnippets::FillArguments),
68     snippet_cap: SnippetCap::new(true),
69     prefer_no_std: false,
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     limit: None,
79 };
80 
completion_list(ra_fixture: &str) -> String81 pub(crate) fn completion_list(ra_fixture: &str) -> String {
82     completion_list_with_config(TEST_CONFIG, ra_fixture, true, None)
83 }
84 
completion_list_no_kw(ra_fixture: &str) -> String85 pub(crate) fn completion_list_no_kw(ra_fixture: &str) -> String {
86     completion_list_with_config(TEST_CONFIG, ra_fixture, false, None)
87 }
88 
completion_list_no_kw_with_private_editable(ra_fixture: &str) -> String89 pub(crate) fn completion_list_no_kw_with_private_editable(ra_fixture: &str) -> String {
90     let mut config = TEST_CONFIG;
91     config.enable_private_editable = true;
92     completion_list_with_config(config, ra_fixture, false, None)
93 }
94 
completion_list_with_trigger_character( ra_fixture: &str, trigger_character: Option<char>, ) -> String95 pub(crate) fn completion_list_with_trigger_character(
96     ra_fixture: &str,
97     trigger_character: Option<char>,
98 ) -> String {
99     completion_list_with_config(TEST_CONFIG, ra_fixture, true, trigger_character)
100 }
101 
completion_list_with_config( config: CompletionConfig, ra_fixture: &str, include_keywords: bool, trigger_character: Option<char>, ) -> String102 fn completion_list_with_config(
103     config: CompletionConfig,
104     ra_fixture: &str,
105     include_keywords: bool,
106     trigger_character: Option<char>,
107 ) -> String {
108     // filter out all but one built-in type completion for smaller test outputs
109     let items = get_all_items(config, ra_fixture, trigger_character);
110     let items = items
111         .into_iter()
112         .filter(|it| it.kind != CompletionItemKind::BuiltinType || it.label == "u32")
113         .filter(|it| include_keywords || it.kind != CompletionItemKind::Keyword)
114         .filter(|it| include_keywords || it.kind != CompletionItemKind::Snippet)
115         .sorted_by_key(|it| (it.kind, it.label.clone(), it.detail.as_ref().map(ToOwned::to_owned)))
116         .collect();
117     render_completion_list(items)
118 }
119 
120 /// Creates analysis from a multi-file fixture, returns positions marked with $0.
position(ra_fixture: &str) -> (RootDatabase, FilePosition)121 pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
122     let change_fixture = ChangeFixture::parse(ra_fixture);
123     let mut database = RootDatabase::default();
124     database.enable_proc_attr_macros();
125     database.apply_change(change_fixture.change);
126     let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
127     let offset = range_or_offset.expect_offset();
128     (database, FilePosition { file_id, offset })
129 }
130 
do_completion(code: &str, kind: CompletionItemKind) -> Vec<CompletionItem>131 pub(crate) fn do_completion(code: &str, kind: CompletionItemKind) -> Vec<CompletionItem> {
132     do_completion_with_config(TEST_CONFIG, code, kind)
133 }
134 
do_completion_with_config( config: CompletionConfig, code: &str, kind: CompletionItemKind, ) -> Vec<CompletionItem>135 pub(crate) fn do_completion_with_config(
136     config: CompletionConfig,
137     code: &str,
138     kind: CompletionItemKind,
139 ) -> Vec<CompletionItem> {
140     get_all_items(config, code, None)
141         .into_iter()
142         .filter(|c| c.kind == kind)
143         .sorted_by(|l, r| l.label.cmp(&r.label))
144         .collect()
145 }
146 
render_completion_list(completions: Vec<CompletionItem>) -> String147 fn render_completion_list(completions: Vec<CompletionItem>) -> String {
148     fn monospace_width(s: &str) -> usize {
149         s.chars().count()
150     }
151     let label_width =
152         completions.iter().map(|it| monospace_width(&it.label)).max().unwrap_or_default().min(22);
153     completions
154         .into_iter()
155         .map(|it| {
156             let tag = it.kind.tag();
157             let var_name = format!("{tag} {}", it.label);
158             let mut buf = var_name;
159             if let Some(detail) = it.detail {
160                 let width = label_width.saturating_sub(monospace_width(&it.label));
161                 format_to!(buf, "{:width$} {}", "", detail, width = width);
162             }
163             if it.deprecated {
164                 format_to!(buf, " DEPRECATED");
165             }
166             format_to!(buf, "\n");
167             buf
168         })
169         .collect()
170 }
171 
172 #[track_caller]
check_edit(what: &str, ra_fixture_before: &str, ra_fixture_after: &str)173 pub(crate) fn check_edit(what: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
174     check_edit_with_config(TEST_CONFIG, what, ra_fixture_before, ra_fixture_after)
175 }
176 
177 #[track_caller]
check_edit_with_config( config: CompletionConfig, what: &str, ra_fixture_before: &str, ra_fixture_after: &str, )178 pub(crate) fn check_edit_with_config(
179     config: CompletionConfig,
180     what: &str,
181     ra_fixture_before: &str,
182     ra_fixture_after: &str,
183 ) {
184     let ra_fixture_after = trim_indent(ra_fixture_after);
185     let (db, position) = position(ra_fixture_before);
186     let completions: Vec<CompletionItem> =
187         crate::completions(&db, &config, position, None).unwrap();
188     let (completion,) = completions
189         .iter()
190         .filter(|it| it.lookup() == what)
191         .collect_tuple()
192         .unwrap_or_else(|| panic!("can't find {what:?} completion in {completions:#?}"));
193     let mut actual = db.file_text(position.file_id).to_string();
194 
195     let mut combined_edit = completion.text_edit.clone();
196 
197     resolve_completion_edits(
198         &db,
199         &config,
200         position,
201         completion
202             .import_to_add
203             .iter()
204             .cloned()
205             .filter_map(|(import_path, import_name)| Some((import_path, import_name))),
206     )
207     .into_iter()
208     .flatten()
209     .for_each(|text_edit| {
210         combined_edit.union(text_edit).expect(
211             "Failed to apply completion resolve changes: change ranges overlap, but should not",
212         )
213     });
214 
215     combined_edit.apply(&mut actual);
216     assert_eq_text!(&ra_fixture_after, &actual)
217 }
218 
check_empty(ra_fixture: &str, expect: Expect)219 fn check_empty(ra_fixture: &str, expect: Expect) {
220     let actual = completion_list(ra_fixture);
221     expect.assert_eq(&actual);
222 }
223 
get_all_items( config: CompletionConfig, code: &str, trigger_character: Option<char>, ) -> Vec<CompletionItem>224 pub(crate) fn get_all_items(
225     config: CompletionConfig,
226     code: &str,
227     trigger_character: Option<char>,
228 ) -> Vec<CompletionItem> {
229     let (db, position) = position(code);
230     let res = crate::completions(&db, &config, position, trigger_character)
231         .map_or_else(Vec::default, Into::into);
232     // validate
233     res.iter().for_each(|it| {
234         let sr = it.source_range;
235         assert!(
236             sr.contains_inclusive(position.offset),
237             "source range {sr:?} does not contain the offset {:?} of the completion request: {it:?}",
238             position.offset
239         );
240     });
241     res
242 }
243 
244 #[test]
test_no_completions_in_for_loop_in_kw_pos()245 fn test_no_completions_in_for_loop_in_kw_pos() {
246     assert_eq!(completion_list(r#"fn foo() { for i i$0 }"#), String::new());
247     assert_eq!(completion_list(r#"fn foo() { for i in$0 }"#), String::new());
248 }
249 
250 #[test]
regression_10042()251 fn regression_10042() {
252     completion_list(
253         r#"
254 macro_rules! preset {
255     ($($x:ident)&&*) => {
256         {
257             let mut v = Vec::new();
258             $(
259                 v.push($x.into());
260             )*
261             v
262         }
263     };
264 }
265 
266 fn foo() {
267     preset!(foo$0);
268 }
269 "#,
270     );
271 }
272 
273 #[test]
no_completions_in_comments()274 fn no_completions_in_comments() {
275     assert_eq!(
276         completion_list(
277             r#"
278 fn test() {
279 let x = 2; // A comment$0
280 }
281 "#,
282         ),
283         String::new(),
284     );
285     assert_eq!(
286         completion_list(
287             r#"
288 /*
289 Some multi-line comment$0
290 */
291 "#,
292         ),
293         String::new(),
294     );
295     assert_eq!(
296         completion_list(
297             r#"
298 /// Some doc comment
299 /// let test$0 = 1
300 "#,
301         ),
302         String::new(),
303     );
304 }
305