]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide-completion/src/tests.rs
Auto merge of #102184 - chenyukang:fix-102087-add-binding-sugg, r=nagisa
[rust.git] / src / tools / rust-analyzer / 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 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 hir::{db::DefDatabase, PrefixKind, Semantics};
27 use ide_db::{
28     base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
29     imports::insert_use::{ImportGranularity, InsertUseConfig},
30     RootDatabase, SnippetCap,
31 };
32 use itertools::Itertools;
33 use stdx::{format_to, trim_indent};
34 use syntax::{AstNode, NodeOrToken, SyntaxElement};
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 };
79
80 pub(crate) fn completion_list(ra_fixture: &str) -> String {
81     completion_list_with_config(TEST_CONFIG, ra_fixture, true, None)
82 }
83
84 pub(crate) fn completion_list_no_kw(ra_fixture: &str) -> String {
85     completion_list_with_config(TEST_CONFIG, ra_fixture, false, None)
86 }
87
88 pub(crate) fn completion_list_no_kw_with_private_editable(ra_fixture: &str) -> String {
89     let mut config = TEST_CONFIG.clone();
90     config.enable_private_editable = true;
91     completion_list_with_config(config, ra_fixture, false, None)
92 }
93
94 pub(crate) fn completion_list_with_trigger_character(
95     ra_fixture: &str,
96     trigger_character: Option<char>,
97 ) -> String {
98     completion_list_with_config(TEST_CONFIG, ra_fixture, true, trigger_character)
99 }
100
101 fn completion_list_with_config(
102     config: CompletionConfig,
103     ra_fixture: &str,
104     include_keywords: bool,
105     trigger_character: Option<char>,
106 ) -> String {
107     // filter out all but one builtintype completion for smaller test outputs
108     let items = get_all_items(config, ra_fixture, trigger_character);
109     let items = items
110         .into_iter()
111         .filter(|it| it.kind() != CompletionItemKind::BuiltinType || it.label() == "u32")
112         .filter(|it| include_keywords || it.kind() != CompletionItemKind::Keyword)
113         .filter(|it| include_keywords || it.kind() != CompletionItemKind::Snippet)
114         .sorted_by_key(|it| (it.kind(), it.label().to_owned(), it.detail().map(ToOwned::to_owned)))
115         .collect();
116     render_completion_list(items)
117 }
118
119 /// Creates analysis from a multi-file fixture, returns positions marked with $0.
120 pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
121     let change_fixture = ChangeFixture::parse(ra_fixture);
122     let mut database = RootDatabase::default();
123     database.set_enable_proc_attr_macros(true);
124     database.apply_change(change_fixture.change);
125     let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
126     let offset = range_or_offset.expect_offset();
127     (database, FilePosition { file_id, offset })
128 }
129
130 pub(crate) fn do_completion(code: &str, kind: CompletionItemKind) -> Vec<CompletionItem> {
131     do_completion_with_config(TEST_CONFIG, code, kind)
132 }
133
134 pub(crate) fn do_completion_with_config(
135     config: CompletionConfig,
136     code: &str,
137     kind: CompletionItemKind,
138 ) -> Vec<CompletionItem> {
139     get_all_items(config, code, None)
140         .into_iter()
141         .filter(|c| c.kind() == kind)
142         .sorted_by(|l, r| l.label().cmp(r.label()))
143         .collect()
144 }
145
146 fn render_completion_list(completions: Vec<CompletionItem>) -> String {
147     fn monospace_width(s: &str) -> usize {
148         s.chars().count()
149     }
150     let label_width =
151         completions.iter().map(|it| monospace_width(it.label())).max().unwrap_or_default().min(22);
152     completions
153         .into_iter()
154         .map(|it| {
155             let tag = it.kind().tag();
156             let var_name = format!("{} {}", tag, it.label());
157             let mut buf = var_name;
158             if let Some(detail) = it.detail() {
159                 let width = label_width.saturating_sub(monospace_width(it.label()));
160                 format_to!(buf, "{:width$} {}", "", detail, width = width);
161             }
162             if it.deprecated() {
163                 format_to!(buf, " DEPRECATED");
164             }
165             format_to!(buf, "\n");
166             buf
167         })
168         .collect()
169 }
170
171 #[track_caller]
172 pub(crate) fn check_edit(what: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
173     check_edit_with_config(TEST_CONFIG, what, ra_fixture_before, ra_fixture_after)
174 }
175
176 #[track_caller]
177 pub(crate) fn check_edit_with_config(
178     config: CompletionConfig,
179     what: &str,
180     ra_fixture_before: &str,
181     ra_fixture_after: &str,
182 ) {
183     let ra_fixture_after = trim_indent(ra_fixture_after);
184     let (db, position) = position(ra_fixture_before);
185     let completions: Vec<CompletionItem> =
186         crate::completions(&db, &config, position, None).unwrap().into();
187     let (completion,) = completions
188         .iter()
189         .filter(|it| it.lookup() == what)
190         .collect_tuple()
191         .unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions));
192     let mut actual = db.file_text(position.file_id).to_string();
193
194     let mut combined_edit = completion.text_edit().to_owned();
195
196     resolve_completion_edits(
197         &db,
198         &config,
199         position,
200         completion.imports_to_add().iter().filter_map(|import_edit| {
201             let import_path = &import_edit.import_path;
202             let import_name = import_path.segments().last()?;
203             Some((import_path.to_string(), import_name.to_string()))
204         }),
205     )
206     .into_iter()
207     .flatten()
208     .for_each(|text_edit| {
209         combined_edit.union(text_edit).expect(
210             "Failed to apply completion resolve changes: change ranges overlap, but should not",
211         )
212     });
213
214     combined_edit.apply(&mut actual);
215     assert_eq_text!(&ra_fixture_after, &actual)
216 }
217
218 pub(crate) fn check_pattern_is_applicable(code: &str, check: impl FnOnce(SyntaxElement) -> bool) {
219     let (db, pos) = position(code);
220
221     let sema = Semantics::new(&db);
222     let original_file = sema.parse(pos.file_id);
223     let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
224     assert!(check(NodeOrToken::Token(token)));
225 }
226
227 pub(crate) fn get_all_items(
228     config: CompletionConfig,
229     code: &str,
230     trigger_character: Option<char>,
231 ) -> Vec<CompletionItem> {
232     let (db, position) = position(code);
233     let res = crate::completions(&db, &config, position, trigger_character)
234         .map_or_else(Vec::default, Into::into);
235     // validate
236     res.iter().for_each(|it| {
237         let sr = it.source_range();
238         assert!(
239             sr.contains_inclusive(position.offset),
240             "source range {sr:?} does not contain the offset {:?} of the completion request: {it:?}",
241             position.offset
242         );
243     });
244     res
245 }
246
247 #[test]
248 fn test_no_completions_required() {
249     assert_eq!(completion_list(r#"fn foo() { for i i$0 }"#), String::new());
250 }
251
252 #[test]
253 fn regression_10042() {
254     completion_list(
255         r#"
256 macro_rules! preset {
257     ($($x:ident)&&*) => {
258         {
259             let mut v = Vec::new();
260             $(
261                 v.push($x.into());
262             )*
263             v
264         }
265     };
266 }
267
268 fn foo() {
269     preset!(foo$0);
270 }
271 "#,
272     );
273 }
274
275 #[test]
276 fn no_completions_in_comments() {
277     assert_eq!(
278         completion_list(
279             r#"
280 fn test() {
281 let x = 2; // A comment$0
282 }
283 "#,
284         ),
285         String::new(),
286     );
287     assert_eq!(
288         completion_list(
289             r#"
290 /*
291 Some multi-line comment$0
292 */
293 "#,
294         ),
295         String::new(),
296     );
297     assert_eq!(
298         completion_list(
299             r#"
300 /// Some doc comment
301 /// let test$0 = 1
302 "#,
303         ),
304         String::new(),
305     );
306 }