2 #[cfg(not(feature = "in-rust-tree"))]
5 use expect_test::expect;
6 use hir::{db::DefDatabase, Semantics};
8 base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt},
9 imports::insert_use::{ImportGranularity, InsertUseConfig},
10 source_change::FileSystemEdit,
11 RootDatabase, SnippetCap,
13 use stdx::{format_to, trim_indent};
14 use syntax::TextRange;
15 use test_utils::{assert_eq_text, extract_offset};
18 assists, handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind,
19 AssistResolveStrategy, Assists, SingleResolve,
22 pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
23 snippet_cap: SnippetCap::new(true),
25 insert_use: InsertUseConfig {
26 granularity: ImportGranularity::Crate,
27 prefix_kind: hir::PrefixKind::Plain,
28 enforce_granularity: true,
30 skip_glob_imports: true,
34 pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
35 RootDatabase::with_single_file(text)
39 pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
40 let ra_fixture_after = trim_indent(ra_fixture_after);
41 check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), None);
44 // There is no way to choose what assist within a group you want to test against,
45 // so this is here to allow you choose.
46 pub(crate) fn check_assist_by_label(
48 ra_fixture_before: &str,
49 ra_fixture_after: &str,
52 let ra_fixture_after = trim_indent(ra_fixture_after);
53 check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), Some(label));
56 // FIXME: instead of having a separate function here, maybe use
57 // `extract_ranges` and mark the target as `<target> </target>` in the
60 pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) {
61 check(assist, ra_fixture, ExpectedResult::Target(target), None);
65 pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
66 check(assist, ra_fixture, ExpectedResult::NotApplicable, None);
69 /// Check assist in unresolved state. Useful to check assists for lazy computation.
71 pub(crate) fn check_assist_unresolved(assist: Handler, ra_fixture: &str) {
72 check(assist, ra_fixture, ExpectedResult::Unresolved, None);
76 fn check_doc_test(assist_id: &str, before: &str, after: &str) {
77 let after = trim_indent(after);
78 let (db, file_id, selection) = RootDatabase::with_range_or_offset(before);
79 let before = db.file_text(file_id).to_string();
80 let frange = FileRange { file_id, range: selection.into() };
82 let assist = assists(&db, &TEST_CONFIG, AssistResolveStrategy::All, frange)
84 .find(|assist| assist.id.0 == assist_id)
87 "\n\nAssist is not applicable: {}\nAvailable assists: {}",
89 assists(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange)
91 .map(|assist| assist.id.0)
99 assist.source_change.expect("Assist did not contain any source changes");
100 let mut actual = before;
101 if let Some(source_file_edit) = source_change.get_source_edit(file_id) {
102 source_file_edit.apply(&mut actual);
106 assert_eq_text!(&after, &actual);
109 enum ExpectedResult<'a> {
117 fn check(handler: Handler, before: &str, expected: ExpectedResult<'_>, assist_label: Option<&str>) {
118 let (mut db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before);
119 db.set_enable_proc_attr_macros(true);
120 let text_without_caret = db.file_text(file_with_caret_id).to_string();
122 let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
124 let sema = Semantics::new(&db);
125 let config = TEST_CONFIG;
126 let ctx = AssistContext::new(sema, &config, frange);
127 let resolve = match expected {
128 ExpectedResult::Unresolved => AssistResolveStrategy::None,
129 _ => AssistResolveStrategy::All,
131 let mut acc = Assists::new(&ctx, resolve);
132 handler(&mut acc, &ctx);
133 let mut res = acc.finish();
135 let assist = match assist_label {
136 Some(label) => res.into_iter().find(|resolved| resolved.label == label),
140 match (assist, expected) {
141 (Some(assist), ExpectedResult::After(after)) => {
143 assist.source_change.expect("Assist did not contain any source changes");
144 let skip_header = source_change.source_file_edits.len() == 1
145 && source_change.file_system_edits.len() == 0;
147 let mut buf = String::new();
148 for (file_id, edit) in source_change.source_file_edits {
149 let mut text = db.file_text(file_id).as_ref().to_owned();
150 edit.apply(&mut text);
152 let sr = db.file_source_root(file_id);
153 let sr = db.source_root(sr);
154 let path = sr.path_for_file(&file_id).unwrap();
155 format_to!(buf, "//- {}\n", path)
160 for file_system_edit in source_change.file_system_edits {
161 let (dst, contents) = match file_system_edit {
162 FileSystemEdit::CreateFile { dst, initial_contents } => (dst, initial_contents),
163 FileSystemEdit::MoveFile { src, dst } => {
164 (dst, db.file_text(src).as_ref().to_owned())
166 FileSystemEdit::MoveDir { src, src_id, dst } => {
167 // temporary placeholder for MoveDir since we are not using MoveDir in ide assists yet.
168 (dst, format!("{:?}\n{:?}", src_id, src))
171 let sr = db.file_source_root(dst.anchor);
172 let sr = db.source_root(sr);
173 let mut base = sr.path_for_file(&dst.anchor).unwrap().clone();
175 let created_file_path = base.join(&dst.path).unwrap();
176 format_to!(buf, "//- {}\n", created_file_path);
177 buf.push_str(&contents);
180 assert_eq_text!(after, &buf);
182 (Some(assist), ExpectedResult::Target(target)) => {
183 let range = assist.target;
184 assert_eq_text!(&text_without_caret[range], target);
186 (Some(assist), ExpectedResult::Unresolved) => assert!(
187 assist.source_change.is_none(),
188 "unresolved assist should not contain source changes"
190 (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),
193 ExpectedResult::After(_) | ExpectedResult::Target(_) | ExpectedResult::Unresolved,
195 panic!("code action is not applicable")
197 (None, ExpectedResult::NotApplicable) => (),
201 fn labels(assists: &[Assist]) -> String {
202 let mut labels = assists
205 let mut label = match &assist.group {
206 Some(g) => g.0.clone(),
207 None => assist.label.to_string(),
212 .collect::<Vec<_>>();
214 labels.into_iter().collect::<String>()
218 fn assist_order_field_struct() {
219 let before = "struct Foo { $0bar: u32 }";
220 let (before_cursor_pos, before) = extract_offset(before);
221 let (db, file_id) = with_single_file(&before);
222 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
223 let assists = assists(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange);
224 let mut assists = assists.iter();
226 assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)");
227 assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method");
228 assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method");
229 assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method");
230 assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`");
234 fn assist_order_if_expr() {
235 let (db, frange) = RootDatabase::with_range(
237 pub fn test_some_range(a: int) -> bool {
238 if let 2..6 = $05$0 {
247 let assists = assists(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange);
248 let expected = labels(&assists);
252 Extract into variable
253 Extract into function
254 Replace if let with match
256 .assert_eq(&expected);
260 fn assist_filter_works() {
261 let (db, frange) = RootDatabase::with_range(
263 pub fn test_some_range(a: int) -> bool {
264 if let 2..6 = $05$0 {
273 let mut cfg = TEST_CONFIG;
274 cfg.allowed = Some(vec![AssistKind::Refactor]);
276 let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange);
277 let expected = labels(&assists);
281 Extract into variable
282 Extract into function
283 Replace if let with match
285 .assert_eq(&expected);
289 let mut cfg = TEST_CONFIG;
290 cfg.allowed = Some(vec![AssistKind::RefactorExtract]);
291 let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange);
292 let expected = labels(&assists);
295 Extract into variable
296 Extract into function
298 .assert_eq(&expected);
302 let mut cfg = TEST_CONFIG;
303 cfg.allowed = Some(vec![AssistKind::QuickFix]);
304 let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange);
305 let expected = labels(&assists);
307 expect![[r#""#]].assert_eq(&expected);
312 fn various_resolve_strategies() {
313 let (db, frange) = RootDatabase::with_range(
315 pub fn test_some_range(a: int) -> bool {
316 if let 2..6 = $05$0 {
325 let mut cfg = TEST_CONFIG;
326 cfg.allowed = Some(vec![AssistKind::RefactorExtract]);
329 let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange);
330 assert_eq!(2, assists.len());
331 let mut assists = assists.into_iter();
333 let extract_into_variable_assist = assists.next().unwrap();
340 label: "Extract into variable",
344 trigger_signature_help: false,
347 .assert_debug_eq(&extract_into_variable_assist);
349 let extract_into_function_assist = assists.next().unwrap();
356 label: "Extract into function",
360 trigger_signature_help: false,
363 .assert_debug_eq(&extract_into_function_assist);
367 let assists = assists(
370 AssistResolveStrategy::Single(SingleResolve {
371 assist_id: "SOMETHING_MISMATCHING".to_string(),
372 assist_kind: AssistKind::RefactorExtract,
376 assert_eq!(2, assists.len());
377 let mut assists = assists.into_iter();
379 let extract_into_variable_assist = assists.next().unwrap();
386 label: "Extract into variable",
390 trigger_signature_help: false,
393 .assert_debug_eq(&extract_into_variable_assist);
395 let extract_into_function_assist = assists.next().unwrap();
402 label: "Extract into function",
406 trigger_signature_help: false,
409 .assert_debug_eq(&extract_into_function_assist);
413 let assists = assists(
416 AssistResolveStrategy::Single(SingleResolve {
417 assist_id: "extract_variable".to_string(),
418 assist_kind: AssistKind::RefactorExtract,
422 assert_eq!(2, assists.len());
423 let mut assists = assists.into_iter();
425 let extract_into_variable_assist = assists.next().unwrap();
432 label: "Extract into variable",
443 insert: "let $0var_name = 5;\n ",
453 file_system_edits: [],
457 trigger_signature_help: false,
460 .assert_debug_eq(&extract_into_variable_assist);
462 let extract_into_function_assist = assists.next().unwrap();
469 label: "Extract into function",
473 trigger_signature_help: false,
476 .assert_debug_eq(&extract_into_function_assist);
480 let assists = assists(&db, &cfg, AssistResolveStrategy::All, frange);
481 assert_eq!(2, assists.len());
482 let mut assists = assists.into_iter();
484 let extract_into_variable_assist = assists.next().unwrap();
491 label: "Extract into variable",
502 insert: "let $0var_name = 5;\n ",
512 file_system_edits: [],
516 trigger_signature_help: false,
519 .assert_debug_eq(&extract_into_variable_assist);
521 let extract_into_function_assist = assists.next().unwrap();
528 label: "Extract into function",
539 insert: "fun_name()",
543 insert: "\n\nfn $0fun_name() -> i32 {\n 5\n}",
549 file_system_edits: [],
553 trigger_signature_help: false,
556 .assert_debug_eq(&extract_into_function_assist);