1
1
//! FIXME: write short doc here
2
2
3
- use hir:: Semantics ;
3
+ use hir:: { Attrs , HirFileId , InFile , Semantics } ;
4
4
use itertools:: Itertools ;
5
5
use ra_ide_db:: RootDatabase ;
6
6
use ra_syntax:: {
7
7
ast:: { self , AstNode , AttrsOwner , ModuleItemOwner , NameOwner } ,
8
- match_ast, SyntaxNode , TextRange ,
8
+ match_ast, SmolStr , SyntaxNode , TextRange ,
9
9
} ;
10
10
11
11
use crate :: FileId ;
@@ -16,6 +16,7 @@ use std::fmt::Display;
16
16
pub struct Runnable {
17
17
pub range : TextRange ,
18
18
pub kind : RunnableKind ,
19
+ pub features_needed : Option < Vec < SmolStr > > ,
19
20
}
20
21
21
22
#[ derive( Debug ) ]
@@ -45,20 +46,24 @@ pub enum RunnableKind {
45
46
pub ( crate ) fn runnables ( db : & RootDatabase , file_id : FileId ) -> Vec < Runnable > {
46
47
let sema = Semantics :: new ( db) ;
47
48
let source_file = sema. parse ( file_id) ;
48
- source_file. syntax ( ) . descendants ( ) . filter_map ( |i| runnable ( & sema, i) ) . collect ( )
49
+ source_file. syntax ( ) . descendants ( ) . filter_map ( |i| runnable ( & sema, i, file_id ) ) . collect ( )
49
50
}
50
51
51
- fn runnable ( sema : & Semantics < RootDatabase > , item : SyntaxNode ) -> Option < Runnable > {
52
+ fn runnable ( sema : & Semantics < RootDatabase > , item : SyntaxNode , file_id : FileId ) -> Option < Runnable > {
52
53
match_ast ! {
53
54
match item {
54
- ast:: FnDef ( it) => runnable_fn( sema, it) ,
55
- ast:: Module ( it) => runnable_mod( sema, it) ,
55
+ ast:: FnDef ( it) => runnable_fn( sema, it, file_id ) ,
56
+ ast:: Module ( it) => runnable_mod( sema, it, file_id ) ,
56
57
_ => None ,
57
58
}
58
59
}
59
60
}
60
61
61
- fn runnable_fn ( sema : & Semantics < RootDatabase > , fn_def : ast:: FnDef ) -> Option < Runnable > {
62
+ fn runnable_fn (
63
+ sema : & Semantics < RootDatabase > ,
64
+ fn_def : ast:: FnDef ,
65
+ file_id : FileId ,
66
+ ) -> Option < Runnable > {
62
67
let name_string = fn_def. name ( ) ?. text ( ) . to_string ( ) ;
63
68
64
69
let kind = if name_string == "main" {
@@ -89,7 +94,11 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
89
94
return None ;
90
95
}
91
96
} ;
92
- Some ( Runnable { range : fn_def. syntax ( ) . text_range ( ) , kind } )
97
+
98
+ let attrs = Attrs :: from_attrs_owner ( sema. db , InFile :: new ( HirFileId :: from ( file_id) , & fn_def) ) ;
99
+ let features_needed = get_features_needed ( attrs) ;
100
+
101
+ Some ( Runnable { range : fn_def. syntax ( ) . text_range ( ) , kind, features_needed } )
93
102
}
94
103
95
104
#[ derive( Debug ) ]
@@ -125,7 +134,11 @@ fn has_doc_test(fn_def: &ast::FnDef) -> bool {
125
134
fn_def. doc_comment_text ( ) . map_or ( false , |comment| comment. contains ( "```" ) )
126
135
}
127
136
128
- fn runnable_mod ( sema : & Semantics < RootDatabase > , module : ast:: Module ) -> Option < Runnable > {
137
+ fn runnable_mod (
138
+ sema : & Semantics < RootDatabase > ,
139
+ module : ast:: Module ,
140
+ file_id : FileId ,
141
+ ) -> Option < Runnable > {
129
142
let has_test_function = module
130
143
. item_list ( ) ?
131
144
. items ( )
@@ -138,11 +151,34 @@ fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<R
138
151
return None ;
139
152
}
140
153
let range = module. syntax ( ) . text_range ( ) ;
141
- let module = sema. to_def ( & module) ?;
154
+ let module_def = sema. to_def ( & module) ?;
155
+
156
+ let path = module_def
157
+ . path_to_root ( sema. db )
158
+ . into_iter ( )
159
+ . rev ( )
160
+ . filter_map ( |it| it. name ( sema. db ) )
161
+ . join ( "::" ) ;
162
+
163
+ let attrs = Attrs :: from_attrs_owner ( sema. db , InFile :: new ( HirFileId :: from ( file_id) , & module) ) ;
164
+ let features_needed = get_features_needed ( attrs) ;
142
165
143
- let path =
144
- module. path_to_root ( sema. db ) . into_iter ( ) . rev ( ) . filter_map ( |it| it. name ( sema. db ) ) . join ( "::" ) ;
145
- Some ( Runnable { range, kind : RunnableKind :: TestMod { path } } )
166
+ Some ( Runnable { range, kind : RunnableKind :: TestMod { path } , features_needed } )
167
+ }
168
+
169
+ fn get_features_needed ( attrs : Attrs ) -> Option < Vec < SmolStr > > {
170
+ let cfg_expr = attrs. by_key ( "cfg" ) . tt_values ( ) . map ( |subtree| ra_cfg:: parse_cfg ( subtree) ) ;
171
+ let features_needed = cfg_expr. fold ( vec ! [ ] , |mut acc, cfg| {
172
+ if let Some ( features_needed) = cfg. minimal_features_needed ( ) {
173
+ acc. extend ( features_needed) ;
174
+ }
175
+ acc
176
+ } ) ;
177
+ if features_needed. is_empty ( ) {
178
+ None
179
+ } else {
180
+ Some ( features_needed)
181
+ }
146
182
}
147
183
148
184
#[ cfg( test) ]
@@ -174,6 +210,7 @@ mod tests {
174
210
Runnable {
175
211
range: 1..21,
176
212
kind: Bin,
213
+ features_needed: None,
177
214
},
178
215
Runnable {
179
216
range: 22..46,
@@ -185,6 +222,7 @@ mod tests {
185
222
ignore: false,
186
223
},
187
224
},
225
+ features_needed: None,
188
226
},
189
227
Runnable {
190
228
range: 47..81,
@@ -196,6 +234,7 @@ mod tests {
196
234
ignore: true,
197
235
},
198
236
},
237
+ features_needed: None,
199
238
},
200
239
]
201
240
"###
@@ -223,6 +262,7 @@ mod tests {
223
262
Runnable {
224
263
range: 1..21,
225
264
kind: Bin,
265
+ features_needed: None,
226
266
},
227
267
Runnable {
228
268
range: 22..64,
@@ -231,6 +271,7 @@ mod tests {
231
271
"foo",
232
272
),
233
273
},
274
+ features_needed: None,
234
275
},
235
276
]
236
277
"###
@@ -258,6 +299,7 @@ mod tests {
258
299
kind: TestMod {
259
300
path: "test_mod",
260
301
},
302
+ features_needed: None,
261
303
},
262
304
Runnable {
263
305
range: 28..57,
@@ -269,6 +311,7 @@ mod tests {
269
311
ignore: false,
270
312
},
271
313
},
314
+ features_needed: None,
272
315
},
273
316
]
274
317
"###
@@ -298,6 +341,7 @@ mod tests {
298
341
kind: TestMod {
299
342
path: "foo::test_mod",
300
343
},
344
+ features_needed: None,
301
345
},
302
346
Runnable {
303
347
range: 46..79,
@@ -309,6 +353,7 @@ mod tests {
309
353
ignore: false,
310
354
},
311
355
},
356
+ features_needed: None,
312
357
},
313
358
]
314
359
"###
@@ -340,6 +385,7 @@ mod tests {
340
385
kind: TestMod {
341
386
path: "foo::bar::test_mod",
342
387
},
388
+ features_needed: None,
343
389
},
344
390
Runnable {
345
391
range: 68..105,
@@ -351,6 +397,80 @@ mod tests {
351
397
ignore: false,
352
398
},
353
399
},
400
+ features_needed: None,
401
+ },
402
+ ]
403
+ "###
404
+ ) ;
405
+ }
406
+
407
+ #[ test]
408
+ fn test_runnables_with_feature ( ) {
409
+ let ( analysis, pos) = analysis_and_position (
410
+ r#"
411
+ //- /lib.rs crate:foo cfg:feature=foo
412
+ <|> //empty
413
+ #[test]
414
+ #[cfg(feature = "foo")]
415
+ fn test_foo1() {}
416
+ "# ,
417
+ ) ;
418
+ let runnables = analysis. runnables ( pos. file_id ) . unwrap ( ) ;
419
+ assert_debug_snapshot ! ( & runnables,
420
+ @r###"
421
+ [
422
+ Runnable {
423
+ range: 1..58,
424
+ kind: Test {
425
+ test_id: Name(
426
+ "test_foo1",
427
+ ),
428
+ attr: TestAttr {
429
+ ignore: false,
430
+ },
431
+ },
432
+ features_needed: Some(
433
+ [
434
+ "foo",
435
+ ],
436
+ ),
437
+ },
438
+ ]
439
+ "###
440
+ ) ;
441
+ }
442
+
443
+ #[ test]
444
+ fn test_runnables_with_features ( ) {
445
+ let ( analysis, pos) = analysis_and_position (
446
+ r#"
447
+ //- /lib.rs crate:foo cfg:feature=foo,feature=bar
448
+ <|> //empty
449
+ #[test]
450
+ #[cfg(all(feature = "foo", feature = "bar"))]
451
+ fn test_foo1() {}
452
+ "# ,
453
+ ) ;
454
+ let runnables = analysis. runnables ( pos. file_id ) . unwrap ( ) ;
455
+ assert_debug_snapshot ! ( & runnables,
456
+ @r###"
457
+ [
458
+ Runnable {
459
+ range: 1..80,
460
+ kind: Test {
461
+ test_id: Name(
462
+ "test_foo1",
463
+ ),
464
+ attr: TestAttr {
465
+ ignore: false,
466
+ },
467
+ },
468
+ features_needed: Some(
469
+ [
470
+ "foo",
471
+ "bar",
472
+ ],
473
+ ),
354
474
},
355
475
]
356
476
"###
0 commit comments