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.rs242
1 files changed, 242 insertions, 0 deletions
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
new file mode 100644
index 000000000..8039a5164
--- /dev/null
+++ b/crates/ra_ide/src/runnables.rs
@@ -0,0 +1,242 @@
1//! FIXME: write short doc here
2
3use hir::Source;
4use itertools::Itertools;
5use ra_db::SourceDatabase;
6use ra_syntax::{
7 ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner},
8 match_ast, SyntaxNode, TextRange,
9};
10
11use crate::{db::RootDatabase, FileId};
12
13#[derive(Debug)]
14pub struct Runnable {
15 pub range: TextRange,
16 pub kind: RunnableKind,
17}
18
19#[derive(Debug)]
20pub enum RunnableKind {
21 Test { name: String },
22 TestMod { path: String },
23 Bench { name: String },
24 Bin,
25}
26
27pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
28 let parse = db.parse(file_id);
29 parse.tree().syntax().descendants().filter_map(|i| runnable(db, file_id, i)).collect()
30}
31
32fn runnable(db: &RootDatabase, file_id: FileId, item: SyntaxNode) -> Option<Runnable> {
33 match_ast! {
34 match item {
35 ast::FnDef(it) => { runnable_fn(it) },
36 ast::Module(it) => { runnable_mod(db, file_id, it) },
37 _ => { None },
38 }
39 }
40}
41
42fn runnable_fn(fn_def: ast::FnDef) -> Option<Runnable> {
43 let name = fn_def.name()?.text().clone();
44 let kind = if name == "main" {
45 RunnableKind::Bin
46 } else if fn_def.has_atom_attr("test") {
47 RunnableKind::Test { name: name.to_string() }
48 } else if fn_def.has_atom_attr("bench") {
49 RunnableKind::Bench { name: name.to_string() }
50 } else {
51 return None;
52 };
53 Some(Runnable { range: fn_def.syntax().text_range(), kind })
54}
55
56fn runnable_mod(db: &RootDatabase, file_id: FileId, module: ast::Module) -> Option<Runnable> {
57 let has_test_function = module
58 .item_list()?
59 .items()
60 .filter_map(|it| match it {
61 ast::ModuleItem::FnDef(it) => Some(it),
62 _ => None,
63 })
64 .any(|f| f.has_atom_attr("test"));
65 if !has_test_function {
66 return None;
67 }
68 let range = module.syntax().text_range();
69 let src = hir::ModuleSource::from_child_node(db, Source::new(file_id.into(), &module.syntax()));
70 let module = hir::Module::from_definition(db, Source::new(file_id.into(), src))?;
71
72 let path = module.path_to_root(db).into_iter().rev().filter_map(|it| it.name(db)).join("::");
73 Some(Runnable { range, kind: RunnableKind::TestMod { path } })
74}
75
76#[cfg(test)]
77mod tests {
78 use insta::assert_debug_snapshot;
79
80 use crate::mock_analysis::analysis_and_position;
81
82 #[test]
83 fn test_runnables() {
84 let (analysis, pos) = analysis_and_position(
85 r#"
86 //- /lib.rs
87 <|> //empty
88 fn main() {}
89
90 #[test]
91 fn test_foo() {}
92
93 #[test]
94 #[ignore]
95 fn test_foo() {}
96 "#,
97 );
98 let runnables = analysis.runnables(pos.file_id).unwrap();
99 assert_debug_snapshot!(&runnables,
100 @r###"
101 [
102 Runnable {
103 range: [1; 21),
104 kind: Bin,
105 },
106 Runnable {
107 range: [22; 46),
108 kind: Test {
109 name: "test_foo",
110 },
111 },
112 Runnable {
113 range: [47; 81),
114 kind: Test {
115 name: "test_foo",
116 },
117 },
118 ]
119 "###
120 );
121 }
122
123 #[test]
124 fn test_runnables_module() {
125 let (analysis, pos) = analysis_and_position(
126 r#"
127 //- /lib.rs
128 <|> //empty
129 mod test_mod {
130 #[test]
131 fn test_foo1() {}
132 }
133 "#,
134 );
135 let runnables = analysis.runnables(pos.file_id).unwrap();
136 assert_debug_snapshot!(&runnables,
137 @r###"
138 [
139 Runnable {
140 range: [1; 59),
141 kind: TestMod {
142 path: "test_mod",
143 },
144 },
145 Runnable {
146 range: [28; 57),
147 kind: Test {
148 name: "test_foo1",
149 },
150 },
151 ]
152 "###
153 );
154 }
155
156 #[test]
157 fn test_runnables_one_depth_layer_module() {
158 let (analysis, pos) = analysis_and_position(
159 r#"
160 //- /lib.rs
161 <|> //empty
162 mod foo {
163 mod test_mod {
164 #[test]
165 fn test_foo1() {}
166 }
167 }
168 "#,
169 );
170 let runnables = analysis.runnables(pos.file_id).unwrap();
171 assert_debug_snapshot!(&runnables,
172 @r###"
173 [
174 Runnable {
175 range: [23; 85),
176 kind: TestMod {
177 path: "foo::test_mod",
178 },
179 },
180 Runnable {
181 range: [46; 79),
182 kind: Test {
183 name: "test_foo1",
184 },
185 },
186 ]
187 "###
188 );
189 }
190
191 #[test]
192 fn test_runnables_multiple_depth_module() {
193 let (analysis, pos) = analysis_and_position(
194 r#"
195 //- /lib.rs
196 <|> //empty
197 mod foo {
198 mod bar {
199 mod test_mod {
200 #[test]
201 fn test_foo1() {}
202 }
203 }
204 }
205 "#,
206 );
207 let runnables = analysis.runnables(pos.file_id).unwrap();
208 assert_debug_snapshot!(&runnables,
209 @r###"
210 [
211 Runnable {
212 range: [41; 115),
213 kind: TestMod {
214 path: "foo::bar::test_mod",
215 },
216 },
217 Runnable {
218 range: [68; 105),
219 kind: Test {
220 name: "test_foo1",
221 },
222 },
223 ]
224 "###
225 );
226 }
227
228 #[test]
229 fn test_runnables_no_test_function_in_module() {
230 let (analysis, pos) = analysis_and_position(
231 r#"
232 //- /lib.rs
233 <|> //empty
234 mod test_mod {
235 fn foo1() {}
236 }
237 "#,
238 );
239 let runnables = analysis.runnables(pos.file_id).unwrap();
240 assert!(runnables.is_empty())
241 }
242}