mirror of
				https://github.com/rust-lang/rust-analyzer.git
				synced 2025-10-31 03:54:42 +00:00 
			
		
		
		
	Merge pull request #18757 from roife/fix-17812
feat: support updating snapshot tests with codelens/hovering/runnables
This commit is contained in:
		
						commit
						a612fc9a16
					
				
					 13 changed files with 540 additions and 97 deletions
				
			
		|  | @ -5933,6 +5933,12 @@ impl HasCrate for Adt { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl HasCrate for Impl { | ||||||
|  |     fn krate(&self, db: &dyn HirDatabase) -> Crate { | ||||||
|  |         self.module(db).krate() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl HasCrate for Module { | impl HasCrate for Module { | ||||||
|     fn krate(&self, _: &dyn HirDatabase) -> Crate { |     fn krate(&self, _: &dyn HirDatabase) -> Crate { | ||||||
|         Module::krate(*self) |         Module::krate(*self) | ||||||
|  |  | ||||||
|  | @ -316,6 +316,11 @@ fn main() { | ||||||
|                                 }, |                                 }, | ||||||
|                                 kind: Bin, |                                 kind: Bin, | ||||||
|                                 cfg: None, |                                 cfg: None, | ||||||
|  |                                 update_test: UpdateTest { | ||||||
|  |                                     expect_test: false, | ||||||
|  |                                     insta: false, | ||||||
|  |                                     snapbox: false, | ||||||
|  |                                 }, | ||||||
|                             }, |                             }, | ||||||
|                         ), |                         ), | ||||||
|                     }, |                     }, | ||||||
|  | @ -401,6 +406,11 @@ fn main() { | ||||||
|                                 }, |                                 }, | ||||||
|                                 kind: Bin, |                                 kind: Bin, | ||||||
|                                 cfg: None, |                                 cfg: None, | ||||||
|  |                                 update_test: UpdateTest { | ||||||
|  |                                     expect_test: false, | ||||||
|  |                                     insta: false, | ||||||
|  |                                     snapbox: false, | ||||||
|  |                                 }, | ||||||
|                             }, |                             }, | ||||||
|                         ), |                         ), | ||||||
|                     }, |                     }, | ||||||
|  | @ -537,6 +547,11 @@ fn main() { | ||||||
|                                 }, |                                 }, | ||||||
|                                 kind: Bin, |                                 kind: Bin, | ||||||
|                                 cfg: None, |                                 cfg: None, | ||||||
|  |                                 update_test: UpdateTest { | ||||||
|  |                                     expect_test: false, | ||||||
|  |                                     insta: false, | ||||||
|  |                                     snapbox: false, | ||||||
|  |                                 }, | ||||||
|                             }, |                             }, | ||||||
|                         ), |                         ), | ||||||
|                     }, |                     }, | ||||||
|  | @ -597,6 +612,11 @@ fn main() {} | ||||||
|                                 }, |                                 }, | ||||||
|                                 kind: Bin, |                                 kind: Bin, | ||||||
|                                 cfg: None, |                                 cfg: None, | ||||||
|  |                                 update_test: UpdateTest { | ||||||
|  |                                     expect_test: false, | ||||||
|  |                                     insta: false, | ||||||
|  |                                     snapbox: false, | ||||||
|  |                                 }, | ||||||
|                             }, |                             }, | ||||||
|                         ), |                         ), | ||||||
|                     }, |                     }, | ||||||
|  | @ -709,6 +729,11 @@ fn main() { | ||||||
|                                 }, |                                 }, | ||||||
|                                 kind: Bin, |                                 kind: Bin, | ||||||
|                                 cfg: None, |                                 cfg: None, | ||||||
|  |                                 update_test: UpdateTest { | ||||||
|  |                                     expect_test: false, | ||||||
|  |                                     insta: false, | ||||||
|  |                                     snapbox: false, | ||||||
|  |                                 }, | ||||||
|                             }, |                             }, | ||||||
|                         ), |                         ), | ||||||
|                     }, |                     }, | ||||||
|  | @ -744,6 +769,20 @@ mod tests { | ||||||
|             "#,
 |             "#,
 | ||||||
|             expect![[r#" |             expect![[r#" | ||||||
|                 [ |                 [ | ||||||
|  |                     Annotation { | ||||||
|  |                         range: 3..7, | ||||||
|  |                         kind: HasReferences { | ||||||
|  |                             pos: FilePositionWrapper { | ||||||
|  |                                 file_id: FileId( | ||||||
|  |                                     0, | ||||||
|  |                                 ), | ||||||
|  |                                 offset: 3, | ||||||
|  |                             }, | ||||||
|  |                             data: Some( | ||||||
|  |                                 [], | ||||||
|  |                             ), | ||||||
|  |                         }, | ||||||
|  |                     }, | ||||||
|                     Annotation { |                     Annotation { | ||||||
|                         range: 3..7, |                         range: 3..7, | ||||||
|                         kind: Runnable( |                         kind: Runnable( | ||||||
|  | @ -760,23 +799,14 @@ mod tests { | ||||||
|                                 }, |                                 }, | ||||||
|                                 kind: Bin, |                                 kind: Bin, | ||||||
|                                 cfg: None, |                                 cfg: None, | ||||||
|  |                                 update_test: UpdateTest { | ||||||
|  |                                     expect_test: false, | ||||||
|  |                                     insta: false, | ||||||
|  |                                     snapbox: false, | ||||||
|  |                                 }, | ||||||
|                             }, |                             }, | ||||||
|                         ), |                         ), | ||||||
|                     }, |                     }, | ||||||
|                     Annotation { |  | ||||||
|                         range: 3..7, |  | ||||||
|                         kind: HasReferences { |  | ||||||
|                             pos: FilePositionWrapper { |  | ||||||
|                                 file_id: FileId( |  | ||||||
|                                     0, |  | ||||||
|                                 ), |  | ||||||
|                                 offset: 3, |  | ||||||
|                             }, |  | ||||||
|                             data: Some( |  | ||||||
|                                 [], |  | ||||||
|                             ), |  | ||||||
|                         }, |  | ||||||
|                     }, |  | ||||||
|                     Annotation { |                     Annotation { | ||||||
|                         range: 18..23, |                         range: 18..23, | ||||||
|                         kind: Runnable( |                         kind: Runnable( | ||||||
|  | @ -796,6 +826,11 @@ mod tests { | ||||||
|                                     path: "tests", |                                     path: "tests", | ||||||
|                                 }, |                                 }, | ||||||
|                                 cfg: None, |                                 cfg: None, | ||||||
|  |                                 update_test: UpdateTest { | ||||||
|  |                                     expect_test: false, | ||||||
|  |                                     insta: false, | ||||||
|  |                                     snapbox: false, | ||||||
|  |                                 }, | ||||||
|                             }, |                             }, | ||||||
|                         ), |                         ), | ||||||
|                     }, |                     }, | ||||||
|  | @ -822,6 +857,11 @@ mod tests { | ||||||
|                                     }, |                                     }, | ||||||
|                                 }, |                                 }, | ||||||
|                                 cfg: None, |                                 cfg: None, | ||||||
|  |                                 update_test: UpdateTest { | ||||||
|  |                                     expect_test: false, | ||||||
|  |                                     insta: false, | ||||||
|  |                                     snapbox: false, | ||||||
|  |                                 }, | ||||||
|                             }, |                             }, | ||||||
|                         ), |                         ), | ||||||
|                     }, |                     }, | ||||||
|  |  | ||||||
|  | @ -3260,6 +3260,11 @@ fn foo_$0test() {} | ||||||
|                             }, |                             }, | ||||||
|                         }, |                         }, | ||||||
|                         cfg: None, |                         cfg: None, | ||||||
|  |                         update_test: UpdateTest { | ||||||
|  |                             expect_test: false, | ||||||
|  |                             insta: false, | ||||||
|  |                             snapbox: false, | ||||||
|  |                         }, | ||||||
|                     }, |                     }, | ||||||
|                 ), |                 ), | ||||||
|             ] |             ] | ||||||
|  | @ -3277,28 +3282,33 @@ mod tests$0 { | ||||||
| } | } | ||||||
| "#,
 | "#,
 | ||||||
|         expect![[r#" |         expect![[r#" | ||||||
|                 [ |             [ | ||||||
|                     Runnable( |                 Runnable( | ||||||
|                         Runnable { |                     Runnable { | ||||||
|                             use_name_in_title: false, |                         use_name_in_title: false, | ||||||
|                             nav: NavigationTarget { |                         nav: NavigationTarget { | ||||||
|                                 file_id: FileId( |                             file_id: FileId( | ||||||
|                                     0, |                                 0, | ||||||
|                                 ), |                             ), | ||||||
|                                 full_range: 0..46, |                             full_range: 0..46, | ||||||
|                                 focus_range: 4..9, |                             focus_range: 4..9, | ||||||
|                                 name: "tests", |                             name: "tests", | ||||||
|                                 kind: Module, |                             kind: Module, | ||||||
|                                 description: "mod tests", |                             description: "mod tests", | ||||||
|                             }, |  | ||||||
|                             kind: TestMod { |  | ||||||
|                                 path: "tests", |  | ||||||
|                             }, |  | ||||||
|                             cfg: None, |  | ||||||
|                         }, |                         }, | ||||||
|                     ), |                         kind: TestMod { | ||||||
|                 ] |                             path: "tests", | ||||||
|             "#]],
 |                         }, | ||||||
|  |                         cfg: None, | ||||||
|  |                         update_test: UpdateTest { | ||||||
|  |                             expect_test: false, | ||||||
|  |                             insta: false, | ||||||
|  |                             snapbox: false, | ||||||
|  |                         }, | ||||||
|  |                     }, | ||||||
|  |                 ), | ||||||
|  |             ] | ||||||
|  |         "#]],
 | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -10029,3 +10039,99 @@ fn bar() { | ||||||
|         "#]],
 |         "#]],
 | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn test_runnables_with_snapshot_tests() { | ||||||
|  |     check_actions( | ||||||
|  |         r#" | ||||||
|  | //- /lib.rs crate:foo deps:expect_test,insta,snapbox
 | ||||||
|  | use expect_test::expect; | ||||||
|  | use insta::assert_debug_snapshot; | ||||||
|  | use snapbox::Assert; | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn test$0() { | ||||||
|  |     let actual = "new25"; | ||||||
|  |     expect!["new25"].assert_eq(&actual); | ||||||
|  |     Assert::new() | ||||||
|  |         .action_env("SNAPSHOTS") | ||||||
|  |         .eq(actual, snapbox::str!["new25"]); | ||||||
|  |     assert_debug_snapshot!(actual); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | //- /lib.rs crate:expect_test
 | ||||||
|  | struct Expect; | ||||||
|  | 
 | ||||||
|  | impl Expect { | ||||||
|  |     fn assert_eq(&self, actual: &str) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[macro_export] | ||||||
|  | macro_rules! expect { | ||||||
|  |     ($e:expr) => Expect; // dummy
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | //- /lib.rs crate:insta
 | ||||||
|  | #[macro_export] | ||||||
|  | macro_rules! assert_debug_snapshot { | ||||||
|  |     ($e:expr) => {}; // dummy
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | //- /lib.rs crate:snapbox
 | ||||||
|  | pub struct Assert; | ||||||
|  | 
 | ||||||
|  | impl Assert { | ||||||
|  |     pub fn new() -> Self { Assert } | ||||||
|  | 
 | ||||||
|  |     pub fn action_env(&self, env: &str) -> &Self { self } | ||||||
|  | 
 | ||||||
|  |     pub fn eq(&self, actual: &str, expected: &str) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[macro_export] | ||||||
|  | macro_rules! str { | ||||||
|  |     ($e:expr) => ""; // dummy
 | ||||||
|  | } | ||||||
|  |         "#,
 | ||||||
|  |         expect![[r#" | ||||||
|  |             [ | ||||||
|  |                 Reference( | ||||||
|  |                     FilePositionWrapper { | ||||||
|  |                         file_id: FileId( | ||||||
|  |                             0, | ||||||
|  |                         ), | ||||||
|  |                         offset: 92, | ||||||
|  |                     }, | ||||||
|  |                 ), | ||||||
|  |                 Runnable( | ||||||
|  |                     Runnable { | ||||||
|  |                         use_name_in_title: false, | ||||||
|  |                         nav: NavigationTarget { | ||||||
|  |                             file_id: FileId( | ||||||
|  |                                 0, | ||||||
|  |                             ), | ||||||
|  |                             full_range: 81..301, | ||||||
|  |                             focus_range: 92..96, | ||||||
|  |                             name: "test", | ||||||
|  |                             kind: Function, | ||||||
|  |                         }, | ||||||
|  |                         kind: Test { | ||||||
|  |                             test_id: Path( | ||||||
|  |                                 "test", | ||||||
|  |                             ), | ||||||
|  |                             attr: TestAttr { | ||||||
|  |                                 ignore: false, | ||||||
|  |                             }, | ||||||
|  |                         }, | ||||||
|  |                         cfg: None, | ||||||
|  |                         update_test: UpdateTest { | ||||||
|  |                             expect_test: true, | ||||||
|  |                             insta: true, | ||||||
|  |                             snapbox: true, | ||||||
|  |                         }, | ||||||
|  |                     }, | ||||||
|  |                 ), | ||||||
|  |             ] | ||||||
|  |         "#]],
 | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,10 +1,11 @@ | ||||||
| use std::fmt; | use std::{fmt, sync::OnceLock}; | ||||||
| 
 | 
 | ||||||
|  | use arrayvec::ArrayVec; | ||||||
| use ast::HasName; | use ast::HasName; | ||||||
| use cfg::{CfgAtom, CfgExpr}; | use cfg::{CfgAtom, CfgExpr}; | ||||||
| use hir::{ | use hir::{ | ||||||
|     db::HirDatabase, sym, AsAssocItem, AttrsWithOwner, HasAttrs, HasCrate, HasSource, HirFileIdExt, |     db::HirDatabase, sym, AsAssocItem, AttrsWithOwner, HasAttrs, HasCrate, HasSource, HirFileIdExt, | ||||||
|     Semantics, |     ModPath, Name, PathKind, Semantics, Symbol, | ||||||
| }; | }; | ||||||
| use ide_assists::utils::{has_test_related_attribute, test_related_attribute_syn}; | use ide_assists::utils::{has_test_related_attribute, test_related_attribute_syn}; | ||||||
| use ide_db::{ | use ide_db::{ | ||||||
|  | @ -15,11 +16,12 @@ use ide_db::{ | ||||||
|     FilePosition, FxHashMap, FxHashSet, RootDatabase, SymbolKind, |     FilePosition, FxHashMap, FxHashSet, RootDatabase, SymbolKind, | ||||||
| }; | }; | ||||||
| use itertools::Itertools; | use itertools::Itertools; | ||||||
|  | use smallvec::SmallVec; | ||||||
| use span::{Edition, TextSize}; | use span::{Edition, TextSize}; | ||||||
| use stdx::format_to; | use stdx::format_to; | ||||||
| use syntax::{ | use syntax::{ | ||||||
|     ast::{self, AstNode}, |     ast::{self, AstNode}, | ||||||
|     SmolStr, SyntaxNode, ToSmolStr, |     format_smolstr, SmolStr, SyntaxNode, ToSmolStr, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use crate::{references, FileId, NavigationTarget, ToNav, TryToNav}; | use crate::{references, FileId, NavigationTarget, ToNav, TryToNav}; | ||||||
|  | @ -30,6 +32,7 @@ pub struct Runnable { | ||||||
|     pub nav: NavigationTarget, |     pub nav: NavigationTarget, | ||||||
|     pub kind: RunnableKind, |     pub kind: RunnableKind, | ||||||
|     pub cfg: Option<CfgExpr>, |     pub cfg: Option<CfgExpr>, | ||||||
|  |     pub update_test: UpdateTest, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone, Hash, PartialEq, Eq)] | #[derive(Debug, Clone, Hash, PartialEq, Eq)] | ||||||
|  | @ -334,14 +337,20 @@ pub(crate) fn runnable_fn( | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |     let fn_source = sema.source(def)?; | ||||||
|     let nav = NavigationTarget::from_named( |     let nav = NavigationTarget::from_named( | ||||||
|         sema.db, |         sema.db, | ||||||
|         def.source(sema.db)?.as_ref().map(|it| it as &dyn ast::HasName), |         fn_source.as_ref().map(|it| it as &dyn ast::HasName), | ||||||
|         SymbolKind::Function, |         SymbolKind::Function, | ||||||
|     ) |     ) | ||||||
|     .call_site(); |     .call_site(); | ||||||
|  | 
 | ||||||
|  |     let file_range = fn_source.syntax().original_file_range_with_macro_call_body(sema.db); | ||||||
|  |     let update_test = | ||||||
|  |         UpdateTest::find_snapshot_macro(sema, &fn_source.file_syntax(sema.db), file_range); | ||||||
|  | 
 | ||||||
|     let cfg = def.attrs(sema.db).cfg(); |     let cfg = def.attrs(sema.db).cfg(); | ||||||
|     Some(Runnable { use_name_in_title: false, nav, kind, cfg }) |     Some(Runnable { use_name_in_title: false, nav, kind, cfg, update_test }) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub(crate) fn runnable_mod( | pub(crate) fn runnable_mod( | ||||||
|  | @ -366,7 +375,22 @@ pub(crate) fn runnable_mod( | ||||||
|     let attrs = def.attrs(sema.db); |     let attrs = def.attrs(sema.db); | ||||||
|     let cfg = attrs.cfg(); |     let cfg = attrs.cfg(); | ||||||
|     let nav = NavigationTarget::from_module_to_decl(sema.db, def).call_site(); |     let nav = NavigationTarget::from_module_to_decl(sema.db, def).call_site(); | ||||||
|     Some(Runnable { use_name_in_title: false, nav, kind: RunnableKind::TestMod { path }, cfg }) | 
 | ||||||
|  |     let module_source = sema.module_definition_node(def); | ||||||
|  |     let module_syntax = module_source.file_syntax(sema.db); | ||||||
|  |     let file_range = hir::FileRange { | ||||||
|  |         file_id: module_source.file_id.original_file(sema.db), | ||||||
|  |         range: module_syntax.text_range(), | ||||||
|  |     }; | ||||||
|  |     let update_test = UpdateTest::find_snapshot_macro(sema, &module_syntax, file_range); | ||||||
|  | 
 | ||||||
|  |     Some(Runnable { | ||||||
|  |         use_name_in_title: false, | ||||||
|  |         nav, | ||||||
|  |         kind: RunnableKind::TestMod { path }, | ||||||
|  |         cfg, | ||||||
|  |         update_test, | ||||||
|  |     }) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub(crate) fn runnable_impl( | pub(crate) fn runnable_impl( | ||||||
|  | @ -392,7 +416,19 @@ pub(crate) fn runnable_impl( | ||||||
|     test_id.retain(|c| c != ' '); |     test_id.retain(|c| c != ' '); | ||||||
|     let test_id = TestId::Path(test_id); |     let test_id = TestId::Path(test_id); | ||||||
| 
 | 
 | ||||||
|     Some(Runnable { use_name_in_title: false, nav, kind: RunnableKind::DocTest { test_id }, cfg }) |     let impl_source = sema.source(*def)?; | ||||||
|  |     let impl_syntax = impl_source.syntax(); | ||||||
|  |     let file_range = impl_syntax.original_file_range_with_macro_call_body(sema.db); | ||||||
|  |     let update_test = | ||||||
|  |         UpdateTest::find_snapshot_macro(sema, &impl_syntax.file_syntax(sema.db), file_range); | ||||||
|  | 
 | ||||||
|  |     Some(Runnable { | ||||||
|  |         use_name_in_title: false, | ||||||
|  |         nav, | ||||||
|  |         kind: RunnableKind::DocTest { test_id }, | ||||||
|  |         cfg, | ||||||
|  |         update_test, | ||||||
|  |     }) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn has_cfg_test(attrs: AttrsWithOwner) -> bool { | fn has_cfg_test(attrs: AttrsWithOwner) -> bool { | ||||||
|  | @ -404,6 +440,8 @@ fn runnable_mod_outline_definition( | ||||||
|     sema: &Semantics<'_, RootDatabase>, |     sema: &Semantics<'_, RootDatabase>, | ||||||
|     def: hir::Module, |     def: hir::Module, | ||||||
| ) -> Option<Runnable> { | ) -> Option<Runnable> { | ||||||
|  |     def.as_source_file_id(sema.db)?; | ||||||
|  | 
 | ||||||
|     if !has_test_function_or_multiple_test_submodules(sema, &def, has_cfg_test(def.attrs(sema.db))) |     if !has_test_function_or_multiple_test_submodules(sema, &def, has_cfg_test(def.attrs(sema.db))) | ||||||
|     { |     { | ||||||
|         return None; |         return None; | ||||||
|  | @ -421,16 +459,22 @@ fn runnable_mod_outline_definition( | ||||||
| 
 | 
 | ||||||
|     let attrs = def.attrs(sema.db); |     let attrs = def.attrs(sema.db); | ||||||
|     let cfg = attrs.cfg(); |     let cfg = attrs.cfg(); | ||||||
|     if def.as_source_file_id(sema.db).is_some() { | 
 | ||||||
|         Some(Runnable { |     let mod_source = sema.module_definition_node(def); | ||||||
|             use_name_in_title: false, |     let mod_syntax = mod_source.file_syntax(sema.db); | ||||||
|             nav: def.to_nav(sema.db).call_site(), |     let file_range = hir::FileRange { | ||||||
|             kind: RunnableKind::TestMod { path }, |         file_id: mod_source.file_id.original_file(sema.db), | ||||||
|             cfg, |         range: mod_syntax.text_range(), | ||||||
|         }) |     }; | ||||||
|     } else { |     let update_test = UpdateTest::find_snapshot_macro(sema, &mod_syntax, file_range); | ||||||
|         None | 
 | ||||||
|     } |     Some(Runnable { | ||||||
|  |         use_name_in_title: false, | ||||||
|  |         nav: def.to_nav(sema.db).call_site(), | ||||||
|  |         kind: RunnableKind::TestMod { path }, | ||||||
|  |         cfg, | ||||||
|  |         update_test, | ||||||
|  |     }) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option<Runnable> { | fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option<Runnable> { | ||||||
|  | @ -495,6 +539,7 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option<Runnable> { | ||||||
|         nav, |         nav, | ||||||
|         kind: RunnableKind::DocTest { test_id }, |         kind: RunnableKind::DocTest { test_id }, | ||||||
|         cfg: attrs.cfg(), |         cfg: attrs.cfg(), | ||||||
|  |         update_test: UpdateTest::default(), | ||||||
|     }; |     }; | ||||||
|     Some(res) |     Some(res) | ||||||
| } | } | ||||||
|  | @ -575,6 +620,128 @@ fn has_test_function_or_multiple_test_submodules( | ||||||
|     number_of_test_submodules > 1 |     number_of_test_submodules > 1 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] | ||||||
|  | pub struct UpdateTest { | ||||||
|  |     pub expect_test: bool, | ||||||
|  |     pub insta: bool, | ||||||
|  |     pub snapbox: bool, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static SNAPSHOT_TEST_MACROS: OnceLock<FxHashMap<&str, Vec<ModPath>>> = OnceLock::new(); | ||||||
|  | 
 | ||||||
|  | impl UpdateTest { | ||||||
|  |     const EXPECT_CRATE: &str = "expect_test"; | ||||||
|  |     const EXPECT_MACROS: &[&str] = &["expect", "expect_file"]; | ||||||
|  | 
 | ||||||
|  |     const INSTA_CRATE: &str = "insta"; | ||||||
|  |     const INSTA_MACROS: &[&str] = &[ | ||||||
|  |         "assert_snapshot", | ||||||
|  |         "assert_debug_snapshot", | ||||||
|  |         "assert_display_snapshot", | ||||||
|  |         "assert_json_snapshot", | ||||||
|  |         "assert_yaml_snapshot", | ||||||
|  |         "assert_ron_snapshot", | ||||||
|  |         "assert_toml_snapshot", | ||||||
|  |         "assert_csv_snapshot", | ||||||
|  |         "assert_compact_json_snapshot", | ||||||
|  |         "assert_compact_debug_snapshot", | ||||||
|  |         "assert_binary_snapshot", | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     const SNAPBOX_CRATE: &str = "snapbox"; | ||||||
|  |     const SNAPBOX_MACROS: &[&str] = &["assert_data_eq", "file", "str"]; | ||||||
|  | 
 | ||||||
|  |     fn find_snapshot_macro( | ||||||
|  |         sema: &Semantics<'_, RootDatabase>, | ||||||
|  |         scope: &SyntaxNode, | ||||||
|  |         file_range: hir::FileRange, | ||||||
|  |     ) -> Self { | ||||||
|  |         fn init<'a>( | ||||||
|  |             krate_name: &'a str, | ||||||
|  |             paths: &[&str], | ||||||
|  |             map: &mut FxHashMap<&'a str, Vec<ModPath>>, | ||||||
|  |         ) { | ||||||
|  |             let mut res = Vec::with_capacity(paths.len()); | ||||||
|  |             let krate = Name::new_symbol_root(Symbol::intern(krate_name)); | ||||||
|  |             for path in paths { | ||||||
|  |                 let segments = [krate.clone(), Name::new_symbol_root(Symbol::intern(path))]; | ||||||
|  |                 let mod_path = ModPath::from_segments(PathKind::Abs, segments); | ||||||
|  |                 res.push(mod_path); | ||||||
|  |             } | ||||||
|  |             map.insert(krate_name, res); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let mod_paths = SNAPSHOT_TEST_MACROS.get_or_init(|| { | ||||||
|  |             let mut map = FxHashMap::default(); | ||||||
|  |             init(Self::EXPECT_CRATE, Self::EXPECT_MACROS, &mut map); | ||||||
|  |             init(Self::INSTA_CRATE, Self::INSTA_MACROS, &mut map); | ||||||
|  |             init(Self::SNAPBOX_CRATE, Self::SNAPBOX_MACROS, &mut map); | ||||||
|  |             map | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         let search_scope = SearchScope::file_range(file_range); | ||||||
|  |         let find_macro = |paths: &[ModPath]| { | ||||||
|  |             for path in paths { | ||||||
|  |                 let Some(items) = sema.resolve_mod_path(scope, path) else { | ||||||
|  |                     continue; | ||||||
|  |                 }; | ||||||
|  |                 for item in items { | ||||||
|  |                     if let hir::ItemInNs::Macros(makro) = item { | ||||||
|  |                         if Definition::Macro(makro) | ||||||
|  |                             .usages(sema) | ||||||
|  |                             .in_scope(&search_scope) | ||||||
|  |                             .at_least_one() | ||||||
|  |                         { | ||||||
|  |                             return true; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             false | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         UpdateTest { | ||||||
|  |             expect_test: find_macro(mod_paths.get(Self::EXPECT_CRATE).unwrap()), | ||||||
|  |             insta: find_macro(mod_paths.get(Self::INSTA_CRATE).unwrap()), | ||||||
|  |             snapbox: find_macro(mod_paths.get(Self::SNAPBOX_CRATE).unwrap()), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn label(&self) -> Option<SmolStr> { | ||||||
|  |         let mut builder: SmallVec<[_; 3]> = SmallVec::new(); | ||||||
|  |         if self.expect_test { | ||||||
|  |             builder.push("Expect"); | ||||||
|  |         } | ||||||
|  |         if self.insta { | ||||||
|  |             builder.push("Insta"); | ||||||
|  |         } | ||||||
|  |         if self.snapbox { | ||||||
|  |             builder.push("Snapbox"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let res: SmolStr = builder.join(" + ").into(); | ||||||
|  |         if res.is_empty() { | ||||||
|  |             None | ||||||
|  |         } else { | ||||||
|  |             Some(format_smolstr!("↺\u{fe0e} Update Tests ({res})")) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn env(&self) -> ArrayVec<(&str, &str), 3> { | ||||||
|  |         let mut env = ArrayVec::new(); | ||||||
|  |         if self.expect_test { | ||||||
|  |             env.push(("UPDATE_EXPECT", "1")); | ||||||
|  |         } | ||||||
|  |         if self.insta { | ||||||
|  |             env.push(("INSTA_UPDATE", "always")); | ||||||
|  |         } | ||||||
|  |         if self.snapbox { | ||||||
|  |             env.push(("SNAPSHOTS", "overwrite")); | ||||||
|  |         } | ||||||
|  |         env | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use expect_test::{expect, Expect}; |     use expect_test::{expect, Expect}; | ||||||
|  | @ -1337,18 +1504,18 @@ mod tests { | ||||||
|                         file_id: FileId( |                         file_id: FileId( | ||||||
|                             0, |                             0, | ||||||
|                         ), |                         ), | ||||||
|                         full_range: 52..115, |                         full_range: 121..185, | ||||||
|                         focus_range: 67..75, |                         focus_range: 136..145, | ||||||
|                         name: "foo_test", |                         name: "foo2_test", | ||||||
|                         kind: Function, |                         kind: Function, | ||||||
|                     }, |                     }, | ||||||
|                     NavigationTarget { |                     NavigationTarget { | ||||||
|                         file_id: FileId( |                         file_id: FileId( | ||||||
|                             0, |                             0, | ||||||
|                         ), |                         ), | ||||||
|                         full_range: 121..185, |                         full_range: 52..115, | ||||||
|                         focus_range: 136..145, |                         focus_range: 67..75, | ||||||
|                         name: "foo2_test", |                         name: "foo_test", | ||||||
|                         kind: Function, |                         kind: Function, | ||||||
|                     }, |                     }, | ||||||
|                 ] |                 ] | ||||||
|  |  | ||||||
|  | @ -119,6 +119,9 @@ config_data! { | ||||||
|         /// Whether to show `Run` action. Only applies when
 |         /// Whether to show `Run` action. Only applies when
 | ||||||
|         /// `#rust-analyzer.hover.actions.enable#` is set.
 |         /// `#rust-analyzer.hover.actions.enable#` is set.
 | ||||||
|         hover_actions_run_enable: bool             = true, |         hover_actions_run_enable: bool             = true, | ||||||
|  |         /// Whether to show `Update Test` action. Only applies when
 | ||||||
|  |         /// `#rust-analyzer.hover.actions.enable#` and `#rust-analyzer.hover.actions.run.enable#` are set.
 | ||||||
|  |         hover_actions_updateTest_enable: bool     = true, | ||||||
| 
 | 
 | ||||||
|         /// Whether to show documentation on hover.
 |         /// Whether to show documentation on hover.
 | ||||||
|         hover_documentation_enable: bool           = true, |         hover_documentation_enable: bool           = true, | ||||||
|  | @ -243,6 +246,9 @@ config_data! { | ||||||
|         /// Whether to show `Run` lens. Only applies when
 |         /// Whether to show `Run` lens. Only applies when
 | ||||||
|         /// `#rust-analyzer.lens.enable#` is set.
 |         /// `#rust-analyzer.lens.enable#` is set.
 | ||||||
|         lens_run_enable: bool              = true, |         lens_run_enable: bool              = true, | ||||||
|  |         /// Whether to show `Update Test` lens. Only applies when
 | ||||||
|  |         /// `#rust-analyzer.lens.enable#` and `#rust-analyzer.lens.run.enable#` are set.
 | ||||||
|  |         lens_updateTest_enable: bool = true, | ||||||
| 
 | 
 | ||||||
|         /// Disable project auto-discovery in favor of explicitly specified set
 |         /// Disable project auto-discovery in favor of explicitly specified set
 | ||||||
|         /// of projects.
 |         /// of projects.
 | ||||||
|  | @ -1161,6 +1167,7 @@ pub struct LensConfig { | ||||||
|     // runnables
 |     // runnables
 | ||||||
|     pub run: bool, |     pub run: bool, | ||||||
|     pub debug: bool, |     pub debug: bool, | ||||||
|  |     pub update_test: bool, | ||||||
|     pub interpret: bool, |     pub interpret: bool, | ||||||
| 
 | 
 | ||||||
|     // implementations
 |     // implementations
 | ||||||
|  | @ -1196,6 +1203,7 @@ impl LensConfig { | ||||||
|     pub fn any(&self) -> bool { |     pub fn any(&self) -> bool { | ||||||
|         self.run |         self.run | ||||||
|             || self.debug |             || self.debug | ||||||
|  |             || self.update_test | ||||||
|             || self.implementations |             || self.implementations | ||||||
|             || self.method_refs |             || self.method_refs | ||||||
|             || self.refs_adt |             || self.refs_adt | ||||||
|  | @ -1208,7 +1216,7 @@ impl LensConfig { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn runnable(&self) -> bool { |     pub fn runnable(&self) -> bool { | ||||||
|         self.run || self.debug |         self.run || self.debug || self.update_test | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn references(&self) -> bool { |     pub fn references(&self) -> bool { | ||||||
|  | @ -1222,6 +1230,7 @@ pub struct HoverActionsConfig { | ||||||
|     pub references: bool, |     pub references: bool, | ||||||
|     pub run: bool, |     pub run: bool, | ||||||
|     pub debug: bool, |     pub debug: bool, | ||||||
|  |     pub update_test: bool, | ||||||
|     pub goto_type_def: bool, |     pub goto_type_def: bool, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -1231,6 +1240,7 @@ impl HoverActionsConfig { | ||||||
|         references: false, |         references: false, | ||||||
|         run: false, |         run: false, | ||||||
|         debug: false, |         debug: false, | ||||||
|  |         update_test: false, | ||||||
|         goto_type_def: false, |         goto_type_def: false, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  | @ -1243,7 +1253,7 @@ impl HoverActionsConfig { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn runnable(&self) -> bool { |     pub fn runnable(&self) -> bool { | ||||||
|         self.run || self.debug |         self.run || self.debug || self.update_test | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -1517,6 +1527,9 @@ impl Config { | ||||||
|             references: enable && self.hover_actions_references_enable().to_owned(), |             references: enable && self.hover_actions_references_enable().to_owned(), | ||||||
|             run: enable && self.hover_actions_run_enable().to_owned(), |             run: enable && self.hover_actions_run_enable().to_owned(), | ||||||
|             debug: enable && self.hover_actions_debug_enable().to_owned(), |             debug: enable && self.hover_actions_debug_enable().to_owned(), | ||||||
|  |             update_test: enable | ||||||
|  |                 && self.hover_actions_run_enable().to_owned() | ||||||
|  |                 && self.hover_actions_updateTest_enable().to_owned(), | ||||||
|             goto_type_def: enable && self.hover_actions_gotoTypeDef_enable().to_owned(), |             goto_type_def: enable && self.hover_actions_gotoTypeDef_enable().to_owned(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -2120,6 +2133,9 @@ impl Config { | ||||||
|         LensConfig { |         LensConfig { | ||||||
|             run: *self.lens_enable() && *self.lens_run_enable(), |             run: *self.lens_enable() && *self.lens_run_enable(), | ||||||
|             debug: *self.lens_enable() && *self.lens_debug_enable(), |             debug: *self.lens_enable() && *self.lens_debug_enable(), | ||||||
|  |             update_test: *self.lens_enable() | ||||||
|  |                 && *self.lens_updateTest_enable() | ||||||
|  |                 && *self.lens_run_enable(), | ||||||
|             interpret: *self.lens_enable() && *self.lens_run_enable() && *self.interpret_tests(), |             interpret: *self.lens_enable() && *self.lens_run_enable() && *self.interpret_tests(), | ||||||
|             implementations: *self.lens_enable() && *self.lens_implementations_enable(), |             implementations: *self.lens_enable() && *self.lens_implementations_enable(), | ||||||
|             method_refs: *self.lens_enable() && *self.lens_references_method_enable(), |             method_refs: *self.lens_enable() && *self.lens_references_method_enable(), | ||||||
|  |  | ||||||
|  | @ -27,7 +27,7 @@ use paths::Utf8PathBuf; | ||||||
| use project_model::{CargoWorkspace, ManifestPath, ProjectWorkspaceKind, TargetKind}; | use project_model::{CargoWorkspace, ManifestPath, ProjectWorkspaceKind, TargetKind}; | ||||||
| use serde_json::json; | use serde_json::json; | ||||||
| use stdx::{format_to, never}; | use stdx::{format_to, never}; | ||||||
| use syntax::{algo, ast, AstNode, TextRange, TextSize}; | use syntax::{TextRange, TextSize}; | ||||||
| use triomphe::Arc; | use triomphe::Arc; | ||||||
| use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath}; | use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath}; | ||||||
| 
 | 
 | ||||||
|  | @ -928,39 +928,32 @@ pub(crate) fn handle_runnables( | ||||||
|     let offset = params.position.and_then(|it| from_proto::offset(&line_index, it).ok()); |     let offset = params.position.and_then(|it| from_proto::offset(&line_index, it).ok()); | ||||||
|     let target_spec = TargetSpec::for_file(&snap, file_id)?; |     let target_spec = TargetSpec::for_file(&snap, file_id)?; | ||||||
| 
 | 
 | ||||||
|     let expect_test = match offset { |  | ||||||
|         Some(offset) => { |  | ||||||
|             let source_file = snap.analysis.parse(file_id)?; |  | ||||||
|             algo::find_node_at_offset::<ast::MacroCall>(source_file.syntax(), offset) |  | ||||||
|                 .and_then(|it| it.path()?.segment()?.name_ref()) |  | ||||||
|                 .map_or(false, |it| it.text() == "expect" || it.text() == "expect_file") |  | ||||||
|         } |  | ||||||
|         None => false, |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     let mut res = Vec::new(); |     let mut res = Vec::new(); | ||||||
|     for runnable in snap.analysis.runnables(file_id)? { |     for runnable in snap.analysis.runnables(file_id)? { | ||||||
|         if should_skip_for_offset(&runnable, offset) { |         if should_skip_for_offset(&runnable, offset) | ||||||
|             continue; |             || should_skip_target(&runnable, target_spec.as_ref()) | ||||||
|         } |         { | ||||||
|         if should_skip_target(&runnable, target_spec.as_ref()) { |  | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         let update_test = runnable.update_test; | ||||||
|         if let Some(mut runnable) = to_proto::runnable(&snap, runnable)? { |         if let Some(mut runnable) = to_proto::runnable(&snap, runnable)? { | ||||||
|             if expect_test { |             if let Some(runnable) = | ||||||
|                 if let lsp_ext::RunnableArgs::Cargo(r) = &mut runnable.args { |                 to_proto::make_update_runnable(&runnable, &update_test.label(), &update_test.env()) | ||||||
|                     runnable.label = format!("{} + expect", runnable.label); |             { | ||||||
|                     r.environment.insert("UPDATE_EXPECT".to_owned(), "1".to_owned()); |                 res.push(runnable); | ||||||
|                     if let Some(TargetSpec::Cargo(CargoTargetSpec { |  | ||||||
|                         sysroot_root: Some(sysroot_root), |  | ||||||
|                         .. |  | ||||||
|                     })) = &target_spec |  | ||||||
|                     { |  | ||||||
|                         r.environment |  | ||||||
|                             .insert("RUSTC_TOOLCHAIN".to_owned(), sysroot_root.to_string()); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |             if let lsp_ext::RunnableArgs::Cargo(r) = &mut runnable.args { | ||||||
|  |                 if let Some(TargetSpec::Cargo(CargoTargetSpec { | ||||||
|  |                     sysroot_root: Some(sysroot_root), | ||||||
|  |                     .. | ||||||
|  |                 })) = &target_spec | ||||||
|  |                 { | ||||||
|  |                     r.environment.insert("RUSTC_TOOLCHAIN".to_owned(), sysroot_root.to_string()); | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|             res.push(runnable); |             res.push(runnable); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -2142,6 +2135,7 @@ fn runnable_action_links( | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let title = runnable.title(); |     let title = runnable.title(); | ||||||
|  |     let update_test = runnable.update_test; | ||||||
|     let r = to_proto::runnable(snap, runnable).ok()??; |     let r = to_proto::runnable(snap, runnable).ok()??; | ||||||
| 
 | 
 | ||||||
|     let mut group = lsp_ext::CommandLinkGroup::default(); |     let mut group = lsp_ext::CommandLinkGroup::default(); | ||||||
|  | @ -2153,7 +2147,15 @@ fn runnable_action_links( | ||||||
| 
 | 
 | ||||||
|     if hover_actions_config.debug && client_commands_config.debug_single { |     if hover_actions_config.debug && client_commands_config.debug_single { | ||||||
|         let dbg_command = to_proto::command::debug_single(&r); |         let dbg_command = to_proto::command::debug_single(&r); | ||||||
|         group.commands.push(to_command_link(dbg_command, r.label)); |         group.commands.push(to_command_link(dbg_command, r.label.clone())); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if hover_actions_config.update_test && client_commands_config.run_single { | ||||||
|  |         let label = update_test.label(); | ||||||
|  |         if let Some(r) = to_proto::make_update_runnable(&r, &label, &update_test.env()) { | ||||||
|  |             let update_command = to_proto::command::run_single(&r, label.unwrap().as_str()); | ||||||
|  |             group.commands.push(to_command_link(update_command, r.label.clone())); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Some(group) |     Some(group) | ||||||
|  |  | ||||||
|  | @ -427,14 +427,14 @@ impl Request for Runnables { | ||||||
|     const METHOD: &'static str = "experimental/runnables"; |     const METHOD: &'static str = "experimental/runnables"; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize, Deserialize, Debug)] | #[derive(Serialize, Deserialize, Debug, Clone)] | ||||||
| #[serde(rename_all = "camelCase")] | #[serde(rename_all = "camelCase")] | ||||||
| pub struct RunnablesParams { | pub struct RunnablesParams { | ||||||
|     pub text_document: TextDocumentIdentifier, |     pub text_document: TextDocumentIdentifier, | ||||||
|     pub position: Option<Position>, |     pub position: Option<Position>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Deserialize, Serialize, Debug)] | #[derive(Deserialize, Serialize, Debug, Clone)] | ||||||
| #[serde(rename_all = "camelCase")] | #[serde(rename_all = "camelCase")] | ||||||
| pub struct Runnable { | pub struct Runnable { | ||||||
|     pub label: String, |     pub label: String, | ||||||
|  | @ -444,7 +444,7 @@ pub struct Runnable { | ||||||
|     pub args: RunnableArgs, |     pub args: RunnableArgs, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Deserialize, Serialize, Debug)] | #[derive(Deserialize, Serialize, Debug, Clone)] | ||||||
| #[serde(rename_all = "camelCase")] | #[serde(rename_all = "camelCase")] | ||||||
| #[serde(untagged)] | #[serde(untagged)] | ||||||
| pub enum RunnableArgs { | pub enum RunnableArgs { | ||||||
|  | @ -452,14 +452,14 @@ pub enum RunnableArgs { | ||||||
|     Shell(ShellRunnableArgs), |     Shell(ShellRunnableArgs), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize, Deserialize, Debug)] | #[derive(Serialize, Deserialize, Debug, Clone)] | ||||||
| #[serde(rename_all = "lowercase")] | #[serde(rename_all = "lowercase")] | ||||||
| pub enum RunnableKind { | pub enum RunnableKind { | ||||||
|     Cargo, |     Cargo, | ||||||
|     Shell, |     Shell, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Deserialize, Serialize, Debug)] | #[derive(Deserialize, Serialize, Debug, Clone)] | ||||||
| #[serde(rename_all = "camelCase")] | #[serde(rename_all = "camelCase")] | ||||||
| pub struct CargoRunnableArgs { | pub struct CargoRunnableArgs { | ||||||
|     #[serde(skip_serializing_if = "FxHashMap::is_empty")] |     #[serde(skip_serializing_if = "FxHashMap::is_empty")] | ||||||
|  | @ -475,7 +475,7 @@ pub struct CargoRunnableArgs { | ||||||
|     pub executable_args: Vec<String>, |     pub executable_args: Vec<String>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Deserialize, Serialize, Debug)] | #[derive(Deserialize, Serialize, Debug, Clone)] | ||||||
| #[serde(rename_all = "camelCase")] | #[serde(rename_all = "camelCase")] | ||||||
| pub struct ShellRunnableArgs { | pub struct ShellRunnableArgs { | ||||||
|     #[serde(skip_serializing_if = "FxHashMap::is_empty")] |     #[serde(skip_serializing_if = "FxHashMap::is_empty")] | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ use itertools::Itertools; | ||||||
| use paths::{Utf8Component, Utf8Prefix}; | use paths::{Utf8Component, Utf8Prefix}; | ||||||
| use semver::VersionReq; | use semver::VersionReq; | ||||||
| use serde_json::to_value; | use serde_json::to_value; | ||||||
|  | use syntax::SmolStr; | ||||||
| use vfs::AbsPath; | use vfs::AbsPath; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|  | @ -1567,6 +1568,7 @@ pub(crate) fn code_lens( | ||||||
|             let line_index = snap.file_line_index(run.nav.file_id)?; |             let line_index = snap.file_line_index(run.nav.file_id)?; | ||||||
|             let annotation_range = range(&line_index, annotation.range); |             let annotation_range = range(&line_index, annotation.range); | ||||||
| 
 | 
 | ||||||
|  |             let update_test = run.update_test; | ||||||
|             let title = run.title(); |             let title = run.title(); | ||||||
|             let can_debug = match run.kind { |             let can_debug = match run.kind { | ||||||
|                 ide::RunnableKind::DocTest { .. } => false, |                 ide::RunnableKind::DocTest { .. } => false, | ||||||
|  | @ -1602,6 +1604,18 @@ pub(crate) fn code_lens( | ||||||
|                             data: None, |                             data: None, | ||||||
|                         }) |                         }) | ||||||
|                     } |                     } | ||||||
|  |                     if lens_config.update_test && client_commands_config.run_single { | ||||||
|  |                         let label = update_test.label(); | ||||||
|  |                         let env = update_test.env(); | ||||||
|  |                         if let Some(r) = make_update_runnable(&r, &label, &env) { | ||||||
|  |                             let command = command::run_single(&r, label.unwrap().as_str()); | ||||||
|  |                             acc.push(lsp_types::CodeLens { | ||||||
|  |                                 range: annotation_range, | ||||||
|  |                                 command: Some(command), | ||||||
|  |                                 data: None, | ||||||
|  |                             }) | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 if lens_config.interpret { |                 if lens_config.interpret { | ||||||
|  | @ -1786,7 +1800,7 @@ pub(crate) mod command { | ||||||
| 
 | 
 | ||||||
|     pub(crate) fn debug_single(runnable: &lsp_ext::Runnable) -> lsp_types::Command { |     pub(crate) fn debug_single(runnable: &lsp_ext::Runnable) -> lsp_types::Command { | ||||||
|         lsp_types::Command { |         lsp_types::Command { | ||||||
|             title: "Debug".into(), |             title: "⚙\u{fe0e} Debug".into(), | ||||||
|             command: "rust-analyzer.debugSingle".into(), |             command: "rust-analyzer.debugSingle".into(), | ||||||
|             arguments: Some(vec![to_value(runnable).unwrap()]), |             arguments: Some(vec![to_value(runnable).unwrap()]), | ||||||
|         } |         } | ||||||
|  | @ -1838,6 +1852,28 @@ pub(crate) mod command { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | pub(crate) fn make_update_runnable( | ||||||
|  |     runnable: &lsp_ext::Runnable, | ||||||
|  |     label: &Option<SmolStr>, | ||||||
|  |     env: &[(&str, &str)], | ||||||
|  | ) -> Option<lsp_ext::Runnable> { | ||||||
|  |     if !matches!(runnable.args, lsp_ext::RunnableArgs::Cargo(_)) { | ||||||
|  |         return None; | ||||||
|  |     } | ||||||
|  |     let label = label.as_ref()?; | ||||||
|  | 
 | ||||||
|  |     let mut runnable = runnable.clone(); | ||||||
|  |     runnable.label = format!("{} + {}", runnable.label, label); | ||||||
|  | 
 | ||||||
|  |     let lsp_ext::RunnableArgs::Cargo(r) = &mut runnable.args else { | ||||||
|  |         unreachable!(); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     r.environment.extend(env.iter().map(|(k, v)| (k.to_string(), v.to_string()))); | ||||||
|  | 
 | ||||||
|  |     Some(runnable) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| pub(crate) fn implementation_title(count: usize) -> String { | pub(crate) fn implementation_title(count: usize) -> String { | ||||||
|     if count == 1 { |     if count == 1 { | ||||||
|         "1 implementation".into() |         "1 implementation".into() | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <!--- | <!--- | ||||||
| lsp/ext.rs hash: 9790509d87670c22 | lsp/ext.rs hash: 512c06cd8b46a21d | ||||||
| 
 | 
 | ||||||
| If you need to change the above hash to make the test pass, please check if you | If you need to change the above hash to make the test pass, please check if you | ||||||
| need to adjust this doc as well and ping this issue: | need to adjust this doc as well and ping this issue: | ||||||
|  |  | ||||||
|  | @ -497,6 +497,12 @@ Whether to show `References` action. Only applies when | ||||||
| Whether to show `Run` action. Only applies when | Whether to show `Run` action. Only applies when | ||||||
| `#rust-analyzer.hover.actions.enable#` is set. | `#rust-analyzer.hover.actions.enable#` is set. | ||||||
| -- | -- | ||||||
|  | [[rust-analyzer.hover.actions.updateTest.enable]]rust-analyzer.hover.actions.updateTest.enable (default: `true`):: | ||||||
|  | + | ||||||
|  | -- | ||||||
|  | Whether to show `Update Test` action. Only applies when | ||||||
|  | `#rust-analyzer.hover.actions.enable#` and `#rust-analyzer.hover.actions.run.enable#` are set. | ||||||
|  | -- | ||||||
| [[rust-analyzer.hover.documentation.enable]]rust-analyzer.hover.documentation.enable (default: `true`):: | [[rust-analyzer.hover.documentation.enable]]rust-analyzer.hover.documentation.enable (default: `true`):: | ||||||
| + | + | ||||||
| -- | -- | ||||||
|  | @ -808,6 +814,12 @@ Only applies when `#rust-analyzer.lens.enable#` is set. | ||||||
| Whether to show `Run` lens. Only applies when | Whether to show `Run` lens. Only applies when | ||||||
| `#rust-analyzer.lens.enable#` is set. | `#rust-analyzer.lens.enable#` is set. | ||||||
| -- | -- | ||||||
|  | [[rust-analyzer.lens.updateTest.enable]]rust-analyzer.lens.updateTest.enable (default: `true`):: | ||||||
|  | + | ||||||
|  | -- | ||||||
|  | Whether to show `Update Test` lens. Only applies when | ||||||
|  | `#rust-analyzer.lens.enable#` and `#rust-analyzer.lens.run.enable#` are set. | ||||||
|  | -- | ||||||
| [[rust-analyzer.linkedProjects]]rust-analyzer.linkedProjects (default: `[]`):: | [[rust-analyzer.linkedProjects]]rust-analyzer.linkedProjects (default: `[]`):: | ||||||
| + | + | ||||||
| -- | -- | ||||||
|  |  | ||||||
|  | @ -407,6 +407,11 @@ | ||||||
|                             "$rustc" |                             "$rustc" | ||||||
|                         ], |                         ], | ||||||
|                         "markdownDescription": "Problem matchers to use for `rust-analyzer.run` command, eg `[\"$rustc\", \"$rust-panic\"]`." |                         "markdownDescription": "Problem matchers to use for `rust-analyzer.run` command, eg `[\"$rustc\", \"$rust-panic\"]`." | ||||||
|  |                     }, | ||||||
|  |                     "rust-analyzer.runnables.askBeforeUpdateTest": { | ||||||
|  |                         "type": "boolean", | ||||||
|  |                         "default": true, | ||||||
|  |                         "markdownDescription": "Ask before updating the test when running it." | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|  | @ -1515,6 +1520,16 @@ | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|  |             { | ||||||
|  |                 "title": "hover", | ||||||
|  |                 "properties": { | ||||||
|  |                     "rust-analyzer.hover.actions.updateTest.enable": { | ||||||
|  |                         "markdownDescription": "Whether to show `Update Test` action. Only applies when\n`#rust-analyzer.hover.actions.enable#` and `#rust-analyzer.hover.actions.run.enable#` are set.", | ||||||
|  |                         "default": true, | ||||||
|  |                         "type": "boolean" | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|             { |             { | ||||||
|                 "title": "hover", |                 "title": "hover", | ||||||
|                 "properties": { |                 "properties": { | ||||||
|  | @ -2295,6 +2310,16 @@ | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|  |             { | ||||||
|  |                 "title": "lens", | ||||||
|  |                 "properties": { | ||||||
|  |                     "rust-analyzer.lens.updateTest.enable": { | ||||||
|  |                         "markdownDescription": "Whether to show `Update Test` lens. Only applies when\n`#rust-analyzer.lens.enable#` and `#rust-analyzer.lens.run.enable#` are set.", | ||||||
|  |                         "default": true, | ||||||
|  |                         "type": "boolean" | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|             { |             { | ||||||
|                 "title": "general", |                 "title": "general", | ||||||
|                 "properties": { |                 "properties": { | ||||||
|  |  | ||||||
|  | @ -1139,11 +1139,37 @@ export function peekTests(ctx: CtxInit): Cmd { | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function isUpdatingTest(runnable: ra.Runnable): boolean { | ||||||
|  |     if (!isCargoRunnableArgs(runnable.args)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const env = runnable.args.environment; | ||||||
|  |     return env ? ["UPDATE_EXPECT", "INSTA_UPDATE", "SNAPSHOTS"].some((key) => key in env) : false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export function runSingle(ctx: CtxInit): Cmd { | export function runSingle(ctx: CtxInit): Cmd { | ||||||
|     return async (runnable: ra.Runnable) => { |     return async (runnable: ra.Runnable) => { | ||||||
|         const editor = ctx.activeRustEditor; |         const editor = ctx.activeRustEditor; | ||||||
|         if (!editor) return; |         if (!editor) return; | ||||||
| 
 | 
 | ||||||
|  |         if (isUpdatingTest(runnable) && ctx.config.askBeforeUpdateTest) { | ||||||
|  |             const selection = await vscode.window.showInformationMessage( | ||||||
|  |                 "rust-analyzer", | ||||||
|  |                 { detail: "Do you want to update tests?", modal: true }, | ||||||
|  |                 "Update Now", | ||||||
|  |                 "Update (and Don't ask again)", | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             if (selection !== "Update Now" && selection !== "Update (and Don't ask again)") { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (selection === "Update (and Don't ask again)") { | ||||||
|  |                 await ctx.config.setAskBeforeUpdateTest(false); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         const task = await createTaskFromRunnable(runnable, ctx.config); |         const task = await createTaskFromRunnable(runnable, ctx.config); | ||||||
|         task.group = vscode.TaskGroup.Build; |         task.group = vscode.TaskGroup.Build; | ||||||
|         task.presentationOptions = { |         task.presentationOptions = { | ||||||
|  |  | ||||||
|  | @ -362,6 +362,13 @@ export class Config { | ||||||
|     get initializeStopped() { |     get initializeStopped() { | ||||||
|         return this.get<boolean>("initializeStopped"); |         return this.get<boolean>("initializeStopped"); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     get askBeforeUpdateTest() { | ||||||
|  |         return this.get<boolean>("runnables.askBeforeUpdateTest"); | ||||||
|  |     } | ||||||
|  |     async setAskBeforeUpdateTest(value: boolean) { | ||||||
|  |         await this.cfg.update("runnables.askBeforeUpdateTest", value, true); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function prepareVSCodeConfig<T>(resp: T): T { | export function prepareVSCodeConfig<T>(resp: T): T { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lukas Wirth
						Lukas Wirth