]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/tests.rs
Make group imports configurable
[rust.git] / crates / ide_assists / src / tests.rs
1 mod generated;
2
3 use expect_test::expect;
4 use hir::Semantics;
5 use ide_db::{
6     base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt},
7     helpers::{
8         insert_use::{InsertUseConfig, MergeBehavior},
9         SnippetCap,
10     },
11     source_change::FileSystemEdit,
12     RootDatabase,
13 };
14 use stdx::{format_to, trim_indent};
15 use syntax::TextRange;
16 use test_utils::{assert_eq_text, extract_offset};
17
18 use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists};
19
20 pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
21     snippet_cap: SnippetCap::new(true),
22     allowed: None,
23     insert_use: InsertUseConfig {
24         merge: Some(MergeBehavior::Full),
25         prefix_kind: hir::PrefixKind::Plain,
26         group: true,
27     },
28 };
29
30 pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
31     RootDatabase::with_single_file(text)
32 }
33
34 pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
35     let ra_fixture_after = trim_indent(ra_fixture_after);
36     check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), None);
37 }
38
39 // There is no way to choose what assist within a group you want to test against,
40 // so this is here to allow you choose.
41 pub(crate) fn check_assist_by_label(
42     assist: Handler,
43     ra_fixture_before: &str,
44     ra_fixture_after: &str,
45     label: &str,
46 ) {
47     let ra_fixture_after = trim_indent(ra_fixture_after);
48     check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), Some(label));
49 }
50
51 // FIXME: instead of having a separate function here, maybe use
52 // `extract_ranges` and mark the target as `<target> </target>` in the
53 // fixture?
54 #[track_caller]
55 pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) {
56     check(assist, ra_fixture, ExpectedResult::Target(target), None);
57 }
58
59 #[track_caller]
60 pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
61     check(assist, ra_fixture, ExpectedResult::NotApplicable, None);
62 }
63
64 #[track_caller]
65 fn check_doc_test(assist_id: &str, before: &str, after: &str) {
66     let after = trim_indent(after);
67     let (db, file_id, selection) = RootDatabase::with_range_or_offset(&before);
68     let before = db.file_text(file_id).to_string();
69     let frange = FileRange { file_id, range: selection.into() };
70
71     let assist = Assist::get(&db, &TEST_CONFIG, true, frange)
72         .into_iter()
73         .find(|assist| assist.id.0 == assist_id)
74         .unwrap_or_else(|| {
75             panic!(
76                 "\n\nAssist is not applicable: {}\nAvailable assists: {}",
77                 assist_id,
78                 Assist::get(&db, &TEST_CONFIG, false, frange)
79                     .into_iter()
80                     .map(|assist| assist.id.0)
81                     .collect::<Vec<_>>()
82                     .join(", ")
83             )
84         });
85
86     let actual = {
87         let source_change = assist.source_change.unwrap();
88         let mut actual = before;
89         if let Some(source_file_edit) = source_change.get_source_edit(file_id) {
90             source_file_edit.apply(&mut actual);
91         }
92         actual
93     };
94     assert_eq_text!(&after, &actual);
95 }
96
97 enum ExpectedResult<'a> {
98     NotApplicable,
99     After(&'a str),
100     Target(&'a str),
101 }
102
103 #[track_caller]
104 fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: Option<&str>) {
105     let (db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before);
106     let text_without_caret = db.file_text(file_with_caret_id).to_string();
107
108     let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
109
110     let sema = Semantics::new(&db);
111     let config = TEST_CONFIG;
112     let ctx = AssistContext::new(sema, &config, frange);
113     let mut acc = Assists::new(&ctx, true);
114     handler(&mut acc, &ctx);
115     let mut res = acc.finish();
116
117     let assist = match assist_label {
118         Some(label) => res.into_iter().find(|resolved| resolved.label == label),
119         None => res.pop(),
120     };
121
122     match (assist, expected) {
123         (Some(assist), ExpectedResult::After(after)) => {
124             let source_change = assist.source_change.unwrap();
125             assert!(!source_change.source_file_edits.is_empty());
126             let skip_header = source_change.source_file_edits.len() == 1
127                 && source_change.file_system_edits.len() == 0;
128
129             let mut buf = String::new();
130             for (file_id, edit) in source_change.source_file_edits {
131                 let mut text = db.file_text(file_id).as_ref().to_owned();
132                 edit.apply(&mut text);
133                 if !skip_header {
134                     let sr = db.file_source_root(file_id);
135                     let sr = db.source_root(sr);
136                     let path = sr.path_for_file(&file_id).unwrap();
137                     format_to!(buf, "//- {}\n", path)
138                 }
139                 buf.push_str(&text);
140             }
141
142             for file_system_edit in source_change.file_system_edits {
143                 if let FileSystemEdit::CreateFile { dst, initial_contents } = file_system_edit {
144                     let sr = db.file_source_root(dst.anchor);
145                     let sr = db.source_root(sr);
146                     let mut base = sr.path_for_file(&dst.anchor).unwrap().clone();
147                     base.pop();
148                     let created_file_path = format!("{}{}", base.to_string(), &dst.path[1..]);
149                     format_to!(buf, "//- {}\n", created_file_path);
150                     buf.push_str(&initial_contents);
151                 }
152             }
153
154             assert_eq_text!(after, &buf);
155         }
156         (Some(assist), ExpectedResult::Target(target)) => {
157             let range = assist.target;
158             assert_eq_text!(&text_without_caret[range], target);
159         }
160         (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),
161         (None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => {
162             panic!("code action is not applicable")
163         }
164         (None, ExpectedResult::NotApplicable) => (),
165     };
166 }
167
168 fn labels(assists: &[Assist]) -> String {
169     let mut labels = assists
170         .iter()
171         .map(|assist| {
172             let mut label = match &assist.group {
173                 Some(g) => g.0.clone(),
174                 None => assist.label.to_string(),
175             };
176             label.push('\n');
177             label
178         })
179         .collect::<Vec<_>>();
180     labels.dedup();
181     labels.into_iter().collect::<String>()
182 }
183
184 #[test]
185 fn assist_order_field_struct() {
186     let before = "struct Foo { $0bar: u32 }";
187     let (before_cursor_pos, before) = extract_offset(before);
188     let (db, file_id) = with_single_file(&before);
189     let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
190     let assists = Assist::get(&db, &TEST_CONFIG, false, frange);
191     let mut assists = assists.iter();
192
193     assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)");
194     assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method");
195     assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method");
196     assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method");
197     assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`");
198 }
199
200 #[test]
201 fn assist_order_if_expr() {
202     let (db, frange) = RootDatabase::with_range(
203         r#"
204 pub fn test_some_range(a: int) -> bool {
205     if let 2..6 = $05$0 {
206         true
207     } else {
208         false
209     }
210 }
211 "#,
212     );
213
214     let assists = Assist::get(&db, &TEST_CONFIG, false, frange);
215     let expected = labels(&assists);
216
217     expect![[r#"
218         Convert integer base
219         Extract into variable
220         Extract into function
221         Replace with match
222     "#]]
223     .assert_eq(&expected);
224 }
225
226 #[test]
227 fn assist_filter_works() {
228     let (db, frange) = RootDatabase::with_range(
229         r#"
230 pub fn test_some_range(a: int) -> bool {
231     if let 2..6 = $05$0 {
232         true
233     } else {
234         false
235     }
236 }
237 "#,
238     );
239     {
240         let mut cfg = TEST_CONFIG;
241         cfg.allowed = Some(vec![AssistKind::Refactor]);
242
243         let assists = Assist::get(&db, &cfg, false, frange);
244         let expected = labels(&assists);
245
246         expect![[r#"
247             Convert integer base
248             Extract into variable
249             Extract into function
250             Replace with match
251         "#]]
252         .assert_eq(&expected);
253     }
254
255     {
256         let mut cfg = TEST_CONFIG;
257         cfg.allowed = Some(vec![AssistKind::RefactorExtract]);
258         let assists = Assist::get(&db, &cfg, false, frange);
259         let expected = labels(&assists);
260
261         expect![[r#"
262             Extract into variable
263             Extract into function
264         "#]]
265         .assert_eq(&expected);
266     }
267
268     {
269         let mut cfg = TEST_CONFIG;
270         cfg.allowed = Some(vec![AssistKind::QuickFix]);
271         let assists = Assist::get(&db, &cfg, false, frange);
272         let expected = labels(&assists);
273
274         expect![[r#""#]].assert_eq(&expected);
275     }
276 }