]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/tests.rs
Remove stale module
[rust.git] / 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 fn_param;
14 mod item_list;
15 mod item;
16 mod pattern;
17 mod predicate;
18 mod proc_macros;
19 mod record;
20 mod type_pos;
21 mod use_tree;
22 mod visibility;
23
24 use std::mem;
25
26 use hir::{db::DefDatabase, PrefixKind, Semantics};
27 use ide_db::{
28     base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
29     helpers::{
30         insert_use::{ImportGranularity, InsertUseConfig},
31         SnippetCap,
32     },
33     RootDatabase,
34 };
35 use itertools::Itertools;
36 use stdx::{format_to, trim_indent};
37 use syntax::{AstNode, NodeOrToken, SyntaxElement};
38 use test_utils::assert_eq_text;
39
40 use crate::{item::CompletionKind, CompletionConfig, CompletionItem};
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     add_call_parenthesis: true,
67     add_call_argument_snippets: true,
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)
81 }
82
83 fn completion_list_with_config(config: CompletionConfig, ra_fixture: &str) -> String {
84     // filter out all but one builtintype completion for smaller test outputs
85     let items = get_all_items(config, ra_fixture);
86     let mut bt_seen = false;
87     let items = items
88         .into_iter()
89         .filter(|it| {
90             it.completion_kind != CompletionKind::BuiltinType || !mem::replace(&mut bt_seen, true)
91         })
92         .collect();
93     render_completion_list(items)
94 }
95
96 /// Creates analysis from a multi-file fixture, returns positions marked with $0.
97 pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
98     let change_fixture = ChangeFixture::parse(ra_fixture);
99     let mut database = RootDatabase::default();
100     database.set_enable_proc_attr_macros(true);
101     database.apply_change(change_fixture.change);
102     let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
103     let offset = range_or_offset.expect_offset();
104     (database, FilePosition { file_id, offset })
105 }
106
107 pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
108     do_completion_with_config(TEST_CONFIG, code, kind)
109 }
110
111 pub(crate) fn do_completion_with_config(
112     config: CompletionConfig,
113     code: &str,
114     kind: CompletionKind,
115 ) -> Vec<CompletionItem> {
116     get_all_items(config, code)
117         .into_iter()
118         .filter(|c| c.completion_kind == kind)
119         .sorted_by(|l, r| l.label().cmp(r.label()))
120         .collect()
121 }
122
123 pub(crate) fn filtered_completion_list(code: &str, kind: CompletionKind) -> String {
124     filtered_completion_list_with_config(TEST_CONFIG, code, kind)
125 }
126
127 pub(crate) fn filtered_completion_list_with_config(
128     config: CompletionConfig,
129     code: &str,
130     kind: CompletionKind,
131 ) -> String {
132     let kind_completions: Vec<CompletionItem> =
133         get_all_items(config, code).into_iter().filter(|c| c.completion_kind == kind).collect();
134     render_completion_list(kind_completions)
135 }
136
137 fn render_completion_list(completions: Vec<CompletionItem>) -> String {
138     fn monospace_width(s: &str) -> usize {
139         s.chars().count()
140     }
141     let label_width =
142         completions.iter().map(|it| monospace_width(it.label())).max().unwrap_or_default().min(22);
143     completions
144         .into_iter()
145         .map(|it| {
146             let tag = it.kind().unwrap().tag();
147             let var_name = format!("{} {}", tag, it.label());
148             let mut buf = var_name;
149             if let Some(detail) = it.detail() {
150                 let width = label_width.saturating_sub(monospace_width(it.label()));
151                 format_to!(buf, "{:width$} {}", "", detail, width = width);
152             }
153             if it.deprecated() {
154                 format_to!(buf, " DEPRECATED");
155             }
156             format_to!(buf, "\n");
157             buf
158         })
159         .collect()
160 }
161
162 pub(crate) fn check_edit(what: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
163     check_edit_with_config(TEST_CONFIG, what, ra_fixture_before, ra_fixture_after)
164 }
165
166 pub(crate) fn check_edit_with_config(
167     config: CompletionConfig,
168     what: &str,
169     ra_fixture_before: &str,
170     ra_fixture_after: &str,
171 ) {
172     let ra_fixture_after = trim_indent(ra_fixture_after);
173     let (db, position) = position(ra_fixture_before);
174     let completions: Vec<CompletionItem> =
175         crate::completions(&db, &config, position).unwrap().into();
176     let (completion,) = completions
177         .iter()
178         .filter(|it| it.lookup() == what)
179         .collect_tuple()
180         .unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions));
181     let mut actual = db.file_text(position.file_id).to_string();
182
183     let mut combined_edit = completion.text_edit().to_owned();
184     completion
185         .imports_to_add()
186         .iter()
187         .filter_map(|edit| edit.to_text_edit(config.insert_use))
188         .for_each(|text_edit| {
189             combined_edit.union(text_edit).expect(
190                 "Failed to apply completion resolve changes: change ranges overlap, but should not",
191             )
192         });
193
194     combined_edit.apply(&mut actual);
195     assert_eq_text!(&ra_fixture_after, &actual)
196 }
197
198 pub(crate) fn check_pattern_is_applicable(code: &str, check: impl FnOnce(SyntaxElement) -> bool) {
199     let (db, pos) = position(code);
200
201     let sema = Semantics::new(&db);
202     let original_file = sema.parse(pos.file_id);
203     let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
204     assert!(check(NodeOrToken::Token(token)));
205 }
206
207 pub(crate) fn check_pattern_is_not_applicable(code: &str, check: fn(SyntaxElement) -> bool) {
208     let (db, pos) = position(code);
209     let sema = Semantics::new(&db);
210     let original_file = sema.parse(pos.file_id);
211     let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
212     assert!(!check(NodeOrToken::Token(token)));
213 }
214
215 pub(crate) fn get_all_items(config: CompletionConfig, code: &str) -> Vec<CompletionItem> {
216     let (db, position) = position(code);
217     crate::completions(&db, &config, position).map_or_else(Vec::default, Into::into)
218 }
219
220 fn check_no_completion(ra_fixture: &str) {
221     let (db, position) = position(ra_fixture);
222
223     assert!(
224         crate::completions(&db, &TEST_CONFIG, position).is_none(),
225         "Completions were generated, but weren't expected"
226     );
227 }
228
229 #[test]
230 fn test_no_completions_required() {
231     cov_mark::check!(no_completion_required);
232     check_no_completion(r#"fn foo() { for i i$0 }"#);
233 }
234
235 #[test]
236 fn regression_10042() {
237     completion_list(
238         r#"
239 macro_rules! preset {
240     ($($x:ident)&&*) => {
241         {
242             let mut v = Vec::new();
243             $(
244                 v.push($x.into());
245             )*
246             v
247         }
248     };
249 }
250
251 fn foo() {
252     preset!(foo$0);
253 }
254 "#,
255     );
256 }