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