1
- use std:: fmt;
1
+ use std:: { fmt, ops :: Not } ;
2
2
3
3
use ast:: HasName ;
4
4
use cfg:: { CfgAtom , CfgExpr } ;
@@ -15,6 +15,7 @@ use ide_db::{
15
15
FilePosition , FxHashMap , FxHashSet , RootDatabase , SymbolKind ,
16
16
} ;
17
17
use itertools:: Itertools ;
18
+ use smallvec:: SmallVec ;
18
19
use span:: { Edition , TextSize } ;
19
20
use stdx:: format_to;
20
21
use syntax:: {
@@ -30,6 +31,7 @@ pub struct Runnable {
30
31
pub nav : NavigationTarget ,
31
32
pub kind : RunnableKind ,
32
33
pub cfg : Option < CfgExpr > ,
34
+ pub update_test : UpdateTest ,
33
35
}
34
36
35
37
#[ derive( Debug , Clone , Hash , PartialEq , Eq ) ]
@@ -334,14 +336,19 @@ pub(crate) fn runnable_fn(
334
336
}
335
337
} ;
336
338
339
+ let fn_source = def. source ( sema. db ) ?;
337
340
let nav = NavigationTarget :: from_named (
338
341
sema. db ,
339
- def . source ( sema . db ) ? . as_ref ( ) . map ( |it| it as & dyn ast:: HasName ) ,
342
+ fn_source . as_ref ( ) . map ( |it| it as & dyn ast:: HasName ) ,
340
343
SymbolKind :: Function ,
341
344
)
342
345
. call_site ( ) ;
346
+
347
+ let file_range = fn_source. syntax ( ) . original_file_range_with_macro_call_body ( sema. db ) ;
348
+ let update_test = TestDefs :: new ( sema, def. krate ( sema. db ) , file_range) . update_test ( ) ;
349
+
343
350
let cfg = def. attrs ( sema. db ) . cfg ( ) ;
344
- Some ( Runnable { use_name_in_title : false , nav, kind, cfg } )
351
+ Some ( Runnable { use_name_in_title : false , nav, kind, cfg, update_test } )
345
352
}
346
353
347
354
pub ( crate ) fn runnable_mod (
@@ -366,7 +373,22 @@ pub(crate) fn runnable_mod(
366
373
let attrs = def. attrs ( sema. db ) ;
367
374
let cfg = attrs. cfg ( ) ;
368
375
let nav = NavigationTarget :: from_module_to_decl ( sema. db , def) . call_site ( ) ;
369
- Some ( Runnable { use_name_in_title : false , nav, kind : RunnableKind :: TestMod { path } , cfg } )
376
+
377
+ let file_range = {
378
+ let src = def. definition_source ( sema. db ) ;
379
+ let file_id = src. file_id . original_file ( sema. db ) ;
380
+ let range = src. file_syntax ( sema. db ) . text_range ( ) ;
381
+ hir:: FileRange { file_id, range }
382
+ } ;
383
+ let update_test = TestDefs :: new ( sema, def. krate ( ) , file_range) . update_test ( ) ;
384
+
385
+ Some ( Runnable {
386
+ use_name_in_title : false ,
387
+ nav,
388
+ kind : RunnableKind :: TestMod { path } ,
389
+ cfg,
390
+ update_test,
391
+ } )
370
392
}
371
393
372
394
pub ( crate ) fn runnable_impl (
@@ -392,7 +414,17 @@ pub(crate) fn runnable_impl(
392
414
test_id. retain ( |c| c != ' ' ) ;
393
415
let test_id = TestId :: Path ( test_id) ;
394
416
395
- Some ( Runnable { use_name_in_title : false , nav, kind : RunnableKind :: DocTest { test_id } , cfg } )
417
+ let impl_source =
418
+ def. source ( sema. db ) ?. syntax ( ) . original_file_range_with_macro_call_body ( sema. db ) ;
419
+ let update_test = TestDefs :: new ( sema, def. krate ( sema. db ) , impl_source) . update_test ( ) ;
420
+
421
+ Some ( Runnable {
422
+ use_name_in_title : false ,
423
+ nav,
424
+ kind : RunnableKind :: DocTest { test_id } ,
425
+ cfg,
426
+ update_test,
427
+ } )
396
428
}
397
429
398
430
fn has_cfg_test ( attrs : AttrsWithOwner ) -> bool {
@@ -404,6 +436,8 @@ fn runnable_mod_outline_definition(
404
436
sema : & Semantics < ' _ , RootDatabase > ,
405
437
def : hir:: Module ,
406
438
) -> Option < Runnable > {
439
+ def. as_source_file_id ( sema. db ) ?;
440
+
407
441
if !has_test_function_or_multiple_test_submodules ( sema, & def, has_cfg_test ( def. attrs ( sema. db ) ) )
408
442
{
409
443
return None ;
@@ -421,16 +455,22 @@ fn runnable_mod_outline_definition(
421
455
422
456
let attrs = def. attrs ( sema. db ) ;
423
457
let cfg = attrs. cfg ( ) ;
424
- if def. as_source_file_id ( sema. db ) . is_some ( ) {
425
- Some ( Runnable {
426
- use_name_in_title : false ,
427
- nav : def. to_nav ( sema. db ) . call_site ( ) ,
428
- kind : RunnableKind :: TestMod { path } ,
429
- cfg,
430
- } )
431
- } else {
432
- None
433
- }
458
+
459
+ let file_range = {
460
+ let src = def. definition_source ( sema. db ) ;
461
+ let file_id = src. file_id . original_file ( sema. db ) ;
462
+ let range = src. file_syntax ( sema. db ) . text_range ( ) ;
463
+ hir:: FileRange { file_id, range }
464
+ } ;
465
+ let update_test = TestDefs :: new ( sema, def. krate ( ) , file_range) . update_test ( ) ;
466
+
467
+ Some ( Runnable {
468
+ use_name_in_title : false ,
469
+ nav : def. to_nav ( sema. db ) . call_site ( ) ,
470
+ kind : RunnableKind :: TestMod { path } ,
471
+ cfg,
472
+ update_test,
473
+ } )
434
474
}
435
475
436
476
fn module_def_doctest ( db : & RootDatabase , def : Definition ) -> Option < Runnable > {
@@ -495,6 +535,7 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option<Runnable> {
495
535
nav,
496
536
kind : RunnableKind :: DocTest { test_id } ,
497
537
cfg : attrs. cfg ( ) ,
538
+ update_test : UpdateTest :: default ( ) ,
498
539
} ;
499
540
Some ( res)
500
541
}
@@ -575,6 +616,106 @@ fn has_test_function_or_multiple_test_submodules(
575
616
number_of_test_submodules > 1
576
617
}
577
618
619
+ struct TestDefs < ' a , ' b > ( & ' a Semantics < ' b , RootDatabase > , hir:: Crate , hir:: FileRange ) ;
620
+
621
+ #[ derive( Debug , Default , Clone , Copy , PartialEq , Eq , Hash ) ]
622
+ pub struct UpdateTest {
623
+ pub expect_test : bool ,
624
+ pub insta : bool ,
625
+ pub snapbox : bool ,
626
+ }
627
+
628
+ impl UpdateTest {
629
+ pub fn label ( & self ) -> Option < SmolStr > {
630
+ let mut builder: SmallVec < [ _ ; 3 ] > = SmallVec :: new ( ) ;
631
+ if self . expect_test {
632
+ builder. push ( "Expect" ) ;
633
+ }
634
+ if self . insta {
635
+ builder. push ( "Insta" ) ;
636
+ }
637
+ if self . snapbox {
638
+ builder. push ( "Snapbox" ) ;
639
+ }
640
+
641
+ let res: SmolStr = builder. join ( " + " ) . into ( ) ;
642
+ res. is_empty ( ) . not ( ) . then_some ( res)
643
+ }
644
+ }
645
+
646
+ impl < ' a , ' b > TestDefs < ' a , ' b > {
647
+ fn new (
648
+ sema : & ' a Semantics < ' b , RootDatabase > ,
649
+ current_krate : hir:: Crate ,
650
+ file_range : hir:: FileRange ,
651
+ ) -> Self {
652
+ Self ( sema, current_krate, file_range)
653
+ }
654
+
655
+ fn update_test ( & self ) -> UpdateTest {
656
+ UpdateTest { expect_test : self . expect_test ( ) , insta : self . insta ( ) , snapbox : self . snapbox ( ) }
657
+ }
658
+
659
+ fn expect_test ( & self ) -> bool {
660
+ self . find_macro ( "expect_test:expect" ) || self . find_macro ( "expect_test::expect_file" )
661
+ }
662
+
663
+ fn insta ( & self ) -> bool {
664
+ self . find_macro ( "insta:assert_snapshot" )
665
+ || self . find_macro ( "insta:assert_debug_snapshot" )
666
+ || self . find_macro ( "insta:assert_display_snapshot" )
667
+ || self . find_macro ( "insta:assert_json_snapshot" )
668
+ || self . find_macro ( "insta:assert_yaml_snapshot" )
669
+ || self . find_macro ( "insta:assert_ron_snapshot" )
670
+ || self . find_macro ( "insta:assert_toml_snapshot" )
671
+ || self . find_macro ( "insta:assert_csv_snapshot" )
672
+ || self . find_macro ( "insta:assert_compact_json_snapshot" )
673
+ || self . find_macro ( "insta:assert_compact_debug_snapshot" )
674
+ || self . find_macro ( "insta:assert_binary_snapshot" )
675
+ }
676
+
677
+ fn snapbox ( & self ) -> bool {
678
+ self . find_macro ( "snapbox:assert_data_eq" )
679
+ || self . find_macro ( "snapbox:file" )
680
+ || self . find_macro ( "snapbox:str" )
681
+ }
682
+
683
+ fn find_macro ( & self , path : & str ) -> bool {
684
+ let Some ( hir:: ScopeDef :: ModuleDef ( hir:: ModuleDef :: Macro ( it) ) ) = self . find_def ( path) else {
685
+ return false ;
686
+ } ;
687
+
688
+ Definition :: Macro ( it)
689
+ . usages ( self . 0 )
690
+ . in_scope ( & SearchScope :: file_range ( self . 2 ) )
691
+ . at_least_one ( )
692
+ }
693
+
694
+ fn find_def ( & self , path : & str ) -> Option < hir:: ScopeDef > {
695
+ let db = self . 0 . db ;
696
+
697
+ let mut path = path. split ( ':' ) ;
698
+ let item = path. next_back ( ) ?;
699
+ let krate = path. next ( ) ?;
700
+ let dep = self . 1 . dependencies ( db) . into_iter ( ) . find ( |dep| dep. name . eq_ident ( krate) ) ?;
701
+
702
+ let mut module = dep. krate . root_module ( ) ;
703
+ for segment in path {
704
+ module = module. children ( db) . find_map ( |child| {
705
+ let name = child. name ( db) ?;
706
+ if name. eq_ident ( segment) {
707
+ Some ( child)
708
+ } else {
709
+ None
710
+ }
711
+ } ) ?;
712
+ }
713
+
714
+ let ( _, def) = module. scope ( db, None ) . into_iter ( ) . find ( |( name, _) | name. eq_ident ( item) ) ?;
715
+ Some ( def)
716
+ }
717
+ }
718
+
578
719
#[ cfg( test) ]
579
720
mod tests {
580
721
use expect_test:: { expect, Expect } ;
@@ -1337,18 +1478,18 @@ mod tests {
1337
1478
file_id: FileId(
1338
1479
0,
1339
1480
),
1340
- full_range: 52..115 ,
1341
- focus_range: 67..75 ,
1342
- name: "foo_test ",
1481
+ full_range: 121..185 ,
1482
+ focus_range: 136..145 ,
1483
+ name: "foo2_test ",
1343
1484
kind: Function,
1344
1485
},
1345
1486
NavigationTarget {
1346
1487
file_id: FileId(
1347
1488
0,
1348
1489
),
1349
- full_range: 121..185 ,
1350
- focus_range: 136..145 ,
1351
- name: "foo2_test ",
1490
+ full_range: 52..115 ,
1491
+ focus_range: 67..75 ,
1492
+ name: "foo_test ",
1352
1493
kind: Function,
1353
1494
},
1354
1495
]
0 commit comments