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