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