aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/runnables.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/runnables.rs')
-rw-r--r--crates/ra_ide/src/runnables.rs440
1 files changed, 382 insertions, 58 deletions
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index 131b8f307..f32ce0d22 100644
--- a/crates/ra_ide/src/runnables.rs
+++ b/crates/ra_ide/src/runnables.rs
@@ -1,21 +1,21 @@
1//! FIXME: write short doc here 1use std::fmt;
2 2
3use hir::{AsAssocItem, Semantics}; 3use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics};
4use itertools::Itertools; 4use itertools::Itertools;
5use ra_cfg::CfgExpr;
5use ra_ide_db::RootDatabase; 6use ra_ide_db::RootDatabase;
6use ra_syntax::{ 7use ra_syntax::{
7 ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner}, 8 ast::{self, AstNode, AttrsOwner, DocCommentsOwner, ModuleItemOwner, NameOwner},
8 match_ast, SyntaxNode, TextRange, 9 match_ast, SyntaxNode,
9}; 10};
10 11
11use crate::FileId; 12use crate::{display::ToNav, FileId, NavigationTarget};
12use ast::DocCommentsOwner;
13use std::fmt::Display;
14 13
15#[derive(Debug)] 14#[derive(Debug)]
16pub struct Runnable { 15pub struct Runnable {
17 pub range: TextRange, 16 pub nav: NavigationTarget,
18 pub kind: RunnableKind, 17 pub kind: RunnableKind,
18 pub cfg_exprs: Vec<CfgExpr>,
19} 19}
20 20
21#[derive(Debug)] 21#[derive(Debug)]
@@ -24,8 +24,8 @@ pub enum TestId {
24 Path(String), 24 Path(String),
25} 25}
26 26
27impl Display for TestId { 27impl fmt::Display for TestId {
28 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 28 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29 match self { 29 match self {
30 TestId::Name(name) => write!(f, "{}", name), 30 TestId::Name(name) => write!(f, "{}", name),
31 TestId::Path(path) => write!(f, "{}", path), 31 TestId::Path(path) => write!(f, "{}", path),
@@ -42,32 +42,47 @@ pub enum RunnableKind {
42 Bin, 42 Bin,
43} 43}
44 44
45// Feature: Run
46//
47// Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
48// location**. Super useful for repeatedly running just a single test. Do bind this
49// to a shortcut!
50//
51// |===
52// | Editor | Action Name
53//
54// | VS Code | **Rust Analyzer: Run**
55// |===
45pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 56pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
46 let sema = Semantics::new(db); 57 let sema = Semantics::new(db);
47 let source_file = sema.parse(file_id); 58 let source_file = sema.parse(file_id);
48 source_file.syntax().descendants().filter_map(|i| runnable(&sema, i)).collect() 59 source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect()
49} 60}
50 61
51fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { 62fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> {
52 match_ast! { 63 match_ast! {
53 match item { 64 match item {
54 ast::FnDef(it) => runnable_fn(sema, it), 65 ast::FnDef(it) => runnable_fn(sema, it, file_id),
55 ast::Module(it) => runnable_mod(sema, it), 66 ast::Module(it) => runnable_mod(sema, it, file_id),
56 _ => None, 67 _ => None,
57 } 68 }
58 } 69 }
59} 70}
60 71
61fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Runnable> { 72fn runnable_fn(
73 sema: &Semantics<RootDatabase>,
74 fn_def: ast::FnDef,
75 file_id: FileId,
76) -> Option<Runnable> {
62 let name_string = fn_def.name()?.text().to_string(); 77 let name_string = fn_def.name()?.text().to_string();
63 78
64 let kind = if name_string == "main" { 79 let kind = if name_string == "main" {
65 RunnableKind::Bin 80 RunnableKind::Bin
66 } else { 81 } else {
67 let test_id = if let Some(module) = sema.to_def(&fn_def).map(|def| def.module(sema.db)) { 82 let test_id = match sema.to_def(&fn_def).map(|def| def.module(sema.db)) {
68 let def = sema.to_def(&fn_def)?; 83 Some(module) => {
69 let impl_trait_name = 84 let def = sema.to_def(&fn_def)?;
70 def.as_assoc_item(sema.db).and_then(|assoc_item| { 85 let impl_trait_name = def.as_assoc_item(sema.db).and_then(|assoc_item| {
71 match assoc_item.container(sema.db) { 86 match assoc_item.container(sema.db) {
72 hir::AssocItemContainer::Trait(trait_item) => { 87 hir::AssocItemContainer::Trait(trait_item) => {
73 Some(trait_item.name(sema.db).to_string()) 88 Some(trait_item.name(sema.db).to_string())
@@ -79,25 +94,25 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
79 } 94 }
80 }); 95 });
81 96
82 let path_iter = module 97 let path_iter = module
83 .path_to_root(sema.db) 98 .path_to_root(sema.db)
84 .into_iter() 99 .into_iter()
85 .rev() 100 .rev()
86 .filter_map(|it| it.name(sema.db)) 101 .filter_map(|it| it.name(sema.db))
87 .map(|name| name.to_string()); 102 .map(|name| name.to_string());
88 103
89 let path = if let Some(impl_trait_name) = impl_trait_name { 104 let path = if let Some(impl_trait_name) = impl_trait_name {
90 path_iter 105 path_iter
91 .chain(std::iter::once(impl_trait_name)) 106 .chain(std::iter::once(impl_trait_name))
92 .chain(std::iter::once(name_string)) 107 .chain(std::iter::once(name_string))
93 .join("::") 108 .join("::")
94 } else { 109 } else {
95 path_iter.chain(std::iter::once(name_string)).join("::") 110 path_iter.chain(std::iter::once(name_string)).join("::")
96 }; 111 };
97 112
98 TestId::Path(path) 113 TestId::Path(path)
99 } else { 114 }
100 TestId::Name(name_string) 115 None => TestId::Name(name_string),
101 }; 116 };
102 117
103 if has_test_related_attribute(&fn_def) { 118 if has_test_related_attribute(&fn_def) {
@@ -111,7 +126,13 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
111 return None; 126 return None;
112 } 127 }
113 }; 128 };
114 Some(Runnable { range: fn_def.syntax().text_range(), kind }) 129
130 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def));
131 let cfg_exprs =
132 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
133
134 let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def));
135 Some(Runnable { nav, kind, cfg_exprs })
115} 136}
116 137
117#[derive(Debug)] 138#[derive(Debug)]
@@ -147,7 +168,11 @@ fn has_doc_test(fn_def: &ast::FnDef) -> bool {
147 fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```")) 168 fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```"))
148} 169}
149 170
150fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> { 171fn runnable_mod(
172 sema: &Semantics<RootDatabase>,
173 module: ast::Module,
174 file_id: FileId,
175) -> Option<Runnable> {
151 let has_test_function = module 176 let has_test_function = module
152 .item_list()? 177 .item_list()?
153 .items() 178 .items()
@@ -159,12 +184,21 @@ fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<R
159 if !has_test_function { 184 if !has_test_function {
160 return None; 185 return None;
161 } 186 }
162 let range = module.syntax().text_range(); 187 let module_def = sema.to_def(&module)?;
163 let module = sema.to_def(&module)?;
164 188
165 let path = 189 let path = module_def
166 module.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::"); 190 .path_to_root(sema.db)
167 Some(Runnable { range, kind: RunnableKind::TestMod { path } }) 191 .into_iter()
192 .rev()
193 .filter_map(|it| it.name(sema.db))
194 .join("::");
195
196 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module));
197 let cfg_exprs =
198 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
199
200 let nav = module_def.to_nav(sema.db);
201 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs })
168} 202}
169 203
170#[cfg(test)] 204#[cfg(test)]
@@ -194,11 +228,38 @@ mod tests {
194 @r###" 228 @r###"
195 [ 229 [
196 Runnable { 230 Runnable {
197 range: 1..21, 231 nav: NavigationTarget {
232 file_id: FileId(
233 1,
234 ),
235 full_range: 1..21,
236 name: "main",
237 kind: FN_DEF,
238 focus_range: Some(
239 12..16,
240 ),
241 container_name: None,
242 description: None,
243 docs: None,
244 },
198 kind: Bin, 245 kind: Bin,
246 cfg_exprs: [],
199 }, 247 },
200 Runnable { 248 Runnable {
201 range: 22..46, 249 nav: NavigationTarget {
250 file_id: FileId(
251 1,
252 ),
253 full_range: 22..46,
254 name: "test_foo",
255 kind: FN_DEF,
256 focus_range: Some(
257 33..41,
258 ),
259 container_name: None,
260 description: None,
261 docs: None,
262 },
202 kind: Test { 263 kind: Test {
203 test_id: Path( 264 test_id: Path(
204 "test_foo", 265 "test_foo",
@@ -207,9 +268,23 @@ mod tests {
207 ignore: false, 268 ignore: false,
208 }, 269 },
209 }, 270 },
271 cfg_exprs: [],
210 }, 272 },
211 Runnable { 273 Runnable {
212 range: 47..81, 274 nav: NavigationTarget {
275 file_id: FileId(
276 1,
277 ),
278 full_range: 47..81,
279 name: "test_foo",
280 kind: FN_DEF,
281 focus_range: Some(
282 68..76,
283 ),
284 container_name: None,
285 description: None,
286 docs: None,
287 },
213 kind: Test { 288 kind: Test {
214 test_id: Path( 289 test_id: Path(
215 "test_foo", 290 "test_foo",
@@ -218,6 +293,7 @@ mod tests {
218 ignore: true, 293 ignore: true,
219 }, 294 },
220 }, 295 },
296 cfg_exprs: [],
221 }, 297 },
222 ] 298 ]
223 "### 299 "###
@@ -243,16 +319,44 @@ mod tests {
243 @r###" 319 @r###"
244 [ 320 [
245 Runnable { 321 Runnable {
246 range: 1..21, 322 nav: NavigationTarget {
323 file_id: FileId(
324 1,
325 ),
326 full_range: 1..21,
327 name: "main",
328 kind: FN_DEF,
329 focus_range: Some(
330 12..16,
331 ),
332 container_name: None,
333 description: None,
334 docs: None,
335 },
247 kind: Bin, 336 kind: Bin,
337 cfg_exprs: [],
248 }, 338 },
249 Runnable { 339 Runnable {
250 range: 22..64, 340 nav: NavigationTarget {
341 file_id: FileId(
342 1,
343 ),
344 full_range: 22..64,
345 name: "foo",
346 kind: FN_DEF,
347 focus_range: Some(
348 56..59,
349 ),
350 container_name: None,
351 description: None,
352 docs: None,
353 },
251 kind: DocTest { 354 kind: DocTest {
252 test_id: Path( 355 test_id: Path(
253 "foo", 356 "foo",
254 ), 357 ),
255 }, 358 },
359 cfg_exprs: [],
256 }, 360 },
257 ] 361 ]
258 "### 362 "###
@@ -281,16 +385,44 @@ mod tests {
281 @r###" 385 @r###"
282 [ 386 [
283 Runnable { 387 Runnable {
284 range: 1..21, 388 nav: NavigationTarget {
389 file_id: FileId(
390 1,
391 ),
392 full_range: 1..21,
393 name: "main",
394 kind: FN_DEF,
395 focus_range: Some(
396 12..16,
397 ),
398 container_name: None,
399 description: None,
400 docs: None,
401 },
285 kind: Bin, 402 kind: Bin,
403 cfg_exprs: [],
286 }, 404 },
287 Runnable { 405 Runnable {
288 range: 51..105, 406 nav: NavigationTarget {
407 file_id: FileId(
408 1,
409 ),
410 full_range: 51..105,
411 name: "foo",
412 kind: FN_DEF,
413 focus_range: Some(
414 97..100,
415 ),
416 container_name: None,
417 description: None,
418 docs: None,
419 },
289 kind: DocTest { 420 kind: DocTest {
290 test_id: Path( 421 test_id: Path(
291 "Data::foo", 422 "Data::foo",
292 ), 423 ),
293 }, 424 },
425 cfg_exprs: [],
294 }, 426 },
295 ] 427 ]
296 "### 428 "###
@@ -314,13 +446,40 @@ mod tests {
314 @r###" 446 @r###"
315 [ 447 [
316 Runnable { 448 Runnable {
317 range: 1..59, 449 nav: NavigationTarget {
450 file_id: FileId(
451 1,
452 ),
453 full_range: 1..59,
454 name: "test_mod",
455 kind: MODULE,
456 focus_range: Some(
457 13..21,
458 ),
459 container_name: None,
460 description: None,
461 docs: None,
462 },
318 kind: TestMod { 463 kind: TestMod {
319 path: "test_mod", 464 path: "test_mod",
320 }, 465 },
466 cfg_exprs: [],
321 }, 467 },
322 Runnable { 468 Runnable {
323 range: 28..57, 469 nav: NavigationTarget {
470 file_id: FileId(
471 1,
472 ),
473 full_range: 28..57,
474 name: "test_foo1",
475 kind: FN_DEF,
476 focus_range: Some(
477 43..52,
478 ),
479 container_name: None,
480 description: None,
481 docs: None,
482 },
324 kind: Test { 483 kind: Test {
325 test_id: Path( 484 test_id: Path(
326 "test_mod::test_foo1", 485 "test_mod::test_foo1",
@@ -329,6 +488,7 @@ mod tests {
329 ignore: false, 488 ignore: false,
330 }, 489 },
331 }, 490 },
491 cfg_exprs: [],
332 }, 492 },
333 ] 493 ]
334 "### 494 "###
@@ -354,13 +514,40 @@ mod tests {
354 @r###" 514 @r###"
355 [ 515 [
356 Runnable { 516 Runnable {
357 range: 23..85, 517 nav: NavigationTarget {
518 file_id: FileId(
519 1,
520 ),
521 full_range: 23..85,
522 name: "test_mod",
523 kind: MODULE,
524 focus_range: Some(
525 27..35,
526 ),
527 container_name: None,
528 description: None,
529 docs: None,
530 },
358 kind: TestMod { 531 kind: TestMod {
359 path: "foo::test_mod", 532 path: "foo::test_mod",
360 }, 533 },
534 cfg_exprs: [],
361 }, 535 },
362 Runnable { 536 Runnable {
363 range: 46..79, 537 nav: NavigationTarget {
538 file_id: FileId(
539 1,
540 ),
541 full_range: 46..79,
542 name: "test_foo1",
543 kind: FN_DEF,
544 focus_range: Some(
545 65..74,
546 ),
547 container_name: None,
548 description: None,
549 docs: None,
550 },
364 kind: Test { 551 kind: Test {
365 test_id: Path( 552 test_id: Path(
366 "foo::test_mod::test_foo1", 553 "foo::test_mod::test_foo1",
@@ -369,6 +556,7 @@ mod tests {
369 ignore: false, 556 ignore: false,
370 }, 557 },
371 }, 558 },
559 cfg_exprs: [],
372 }, 560 },
373 ] 561 ]
374 "### 562 "###
@@ -396,13 +584,40 @@ mod tests {
396 @r###" 584 @r###"
397 [ 585 [
398 Runnable { 586 Runnable {
399 range: 41..115, 587 nav: NavigationTarget {
588 file_id: FileId(
589 1,
590 ),
591 full_range: 41..115,
592 name: "test_mod",
593 kind: MODULE,
594 focus_range: Some(
595 45..53,
596 ),
597 container_name: None,
598 description: None,
599 docs: None,
600 },
400 kind: TestMod { 601 kind: TestMod {
401 path: "foo::bar::test_mod", 602 path: "foo::bar::test_mod",
402 }, 603 },
604 cfg_exprs: [],
403 }, 605 },
404 Runnable { 606 Runnable {
405 range: 68..105, 607 nav: NavigationTarget {
608 file_id: FileId(
609 1,
610 ),
611 full_range: 68..105,
612 name: "test_foo1",
613 kind: FN_DEF,
614 focus_range: Some(
615 91..100,
616 ),
617 container_name: None,
618 description: None,
619 docs: None,
620 },
406 kind: Test { 621 kind: Test {
407 test_id: Path( 622 test_id: Path(
408 "foo::bar::test_mod::test_foo1", 623 "foo::bar::test_mod::test_foo1",
@@ -411,6 +626,115 @@ mod tests {
411 ignore: false, 626 ignore: false,
412 }, 627 },
413 }, 628 },
629 cfg_exprs: [],
630 },
631 ]
632 "###
633 );
634 }
635
636 #[test]
637 fn test_runnables_with_feature() {
638 let (analysis, pos) = analysis_and_position(
639 r#"
640 //- /lib.rs crate:foo cfg:feature=foo
641 <|> //empty
642 #[test]
643 #[cfg(feature = "foo")]
644 fn test_foo1() {}
645 "#,
646 );
647 let runnables = analysis.runnables(pos.file_id).unwrap();
648 assert_debug_snapshot!(&runnables,
649 @r###"
650 [
651 Runnable {
652 nav: NavigationTarget {
653 file_id: FileId(
654 1,
655 ),
656 full_range: 1..58,
657 name: "test_foo1",
658 kind: FN_DEF,
659 focus_range: Some(
660 44..53,
661 ),
662 container_name: None,
663 description: None,
664 docs: None,
665 },
666 kind: Test {
667 test_id: Path(
668 "test_foo1",
669 ),
670 attr: TestAttr {
671 ignore: false,
672 },
673 },
674 cfg_exprs: [
675 KeyValue {
676 key: "feature",
677 value: "foo",
678 },
679 ],
680 },
681 ]
682 "###
683 );
684 }
685
686 #[test]
687 fn test_runnables_with_features() {
688 let (analysis, pos) = analysis_and_position(
689 r#"
690 //- /lib.rs crate:foo cfg:feature=foo,feature=bar
691 <|> //empty
692 #[test]
693 #[cfg(all(feature = "foo", feature = "bar"))]
694 fn test_foo1() {}
695 "#,
696 );
697 let runnables = analysis.runnables(pos.file_id).unwrap();
698 assert_debug_snapshot!(&runnables,
699 @r###"
700 [
701 Runnable {
702 nav: NavigationTarget {
703 file_id: FileId(
704 1,
705 ),
706 full_range: 1..80,
707 name: "test_foo1",
708 kind: FN_DEF,
709 focus_range: Some(
710 66..75,
711 ),
712 container_name: None,
713 description: None,
714 docs: None,
715 },
716 kind: Test {
717 test_id: Path(
718 "test_foo1",
719 ),
720 attr: TestAttr {
721 ignore: false,
722 },
723 },
724 cfg_exprs: [
725 All(
726 [
727 KeyValue {
728 key: "feature",
729 value: "foo",
730 },
731 KeyValue {
732 key: "feature",
733 value: "bar",
734 },
735 ],
736 ),
737 ],
414 }, 738 },
415 ] 739 ]
416 "### 740 "###