From 1b0c7701cc97cd7bef8bb9729011d4cf291a60c5 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 13 Aug 2020 17:42:52 +0200 Subject: Rename ra_ide -> ide --- crates/ide/src/goto_implementation.rs | 229 ++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 crates/ide/src/goto_implementation.rs (limited to 'crates/ide/src/goto_implementation.rs') diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs new file mode 100644 index 000000000..f503f4ec5 --- /dev/null +++ b/crates/ide/src/goto_implementation.rs @@ -0,0 +1,229 @@ +use hir::{Crate, ImplDef, Semantics}; +use ide_db::RootDatabase; +use syntax::{algo::find_node_at_offset, ast, AstNode}; + +use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; + +// Feature: Go to Implementation +// +// Navigates to the impl block of structs, enums or traits. Also implemented as a code lens. +// +// |=== +// | Editor | Shortcut +// +// | VS Code | kbd:[Ctrl+F12] +// |=== +pub(crate) fn goto_implementation( + db: &RootDatabase, + position: FilePosition, +) -> Option>> { + let sema = Semantics::new(db); + let source_file = sema.parse(position.file_id); + let syntax = source_file.syntax().clone(); + + let krate = sema.to_module_def(position.file_id)?.krate(); + + if let Some(nominal_def) = find_node_at_offset::(&syntax, position.offset) { + return Some(RangeInfo::new( + nominal_def.syntax().text_range(), + impls_for_def(&sema, &nominal_def, krate)?, + )); + } else if let Some(trait_def) = find_node_at_offset::(&syntax, position.offset) { + return Some(RangeInfo::new( + trait_def.syntax().text_range(), + impls_for_trait(&sema, &trait_def, krate)?, + )); + } + + None +} + +fn impls_for_def( + sema: &Semantics, + node: &ast::AdtDef, + krate: Crate, +) -> Option> { + let ty = match node { + ast::AdtDef::Struct(def) => sema.to_def(def)?.ty(sema.db), + ast::AdtDef::Enum(def) => sema.to_def(def)?.ty(sema.db), + ast::AdtDef::Union(def) => sema.to_def(def)?.ty(sema.db), + }; + + let impls = ImplDef::all_in_crate(sema.db, krate); + + Some( + impls + .into_iter() + .filter(|impl_def| ty.is_equal_for_find_impls(&impl_def.target_ty(sema.db))) + .map(|imp| imp.to_nav(sema.db)) + .collect(), + ) +} + +fn impls_for_trait( + sema: &Semantics, + node: &ast::Trait, + krate: Crate, +) -> Option> { + let tr = sema.to_def(node)?; + + let impls = ImplDef::for_trait(sema.db, krate, tr); + + Some(impls.into_iter().map(|imp| imp.to_nav(sema.db)).collect()) +} + +#[cfg(test)] +mod tests { + use base_db::FileRange; + + use crate::mock_analysis::MockAnalysis; + + fn check(ra_fixture: &str) { + let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture); + let annotations = mock.annotations(); + let analysis = mock.analysis(); + + let navs = analysis.goto_implementation(position).unwrap().unwrap().info; + + let key = |frange: &FileRange| (frange.file_id, frange.range.start()); + + let mut expected = annotations + .into_iter() + .map(|(range, data)| { + assert!(data.is_empty()); + range + }) + .collect::>(); + expected.sort_by_key(key); + + let mut actual = navs + .into_iter() + .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() }) + .collect::>(); + actual.sort_by_key(key); + + assert_eq!(expected, actual); + } + + #[test] + fn goto_implementation_works() { + check( + r#" +struct Foo<|>; +impl Foo {} + //^^^ +"#, + ); + } + + #[test] + fn goto_implementation_works_multiple_blocks() { + check( + r#" +struct Foo<|>; +impl Foo {} + //^^^ +impl Foo {} + //^^^ +"#, + ); + } + + #[test] + fn goto_implementation_works_multiple_mods() { + check( + r#" +struct Foo<|>; +mod a { + impl super::Foo {} + //^^^^^^^^^^ +} +mod b { + impl super::Foo {} + //^^^^^^^^^^ +} +"#, + ); + } + + #[test] + fn goto_implementation_works_multiple_files() { + check( + r#" +//- /lib.rs +struct Foo<|>; +mod a; +mod b; +//- /a.rs +impl crate::Foo {} + //^^^^^^^^^^ +//- /b.rs +impl crate::Foo {} + //^^^^^^^^^^ +"#, + ); + } + + #[test] + fn goto_implementation_for_trait() { + check( + r#" +trait T<|> {} +struct Foo; +impl T for Foo {} + //^^^ +"#, + ); + } + + #[test] + fn goto_implementation_for_trait_multiple_files() { + check( + r#" +//- /lib.rs +trait T<|> {}; +struct Foo; +mod a; +mod b; +//- /a.rs +impl crate::T for crate::Foo {} + //^^^^^^^^^^ +//- /b.rs +impl crate::T for crate::Foo {} + //^^^^^^^^^^ + "#, + ); + } + + #[test] + fn goto_implementation_all_impls() { + check( + r#" +//- /lib.rs +trait T {} +struct Foo<|>; +impl Foo {} + //^^^ +impl T for Foo {} + //^^^ +impl T for &Foo {} + //^^^^ +"#, + ); + } + + #[test] + fn goto_implementation_to_builtin_derive() { + check( + r#" + #[derive(Copy)] +//^^^^^^^^^^^^^^^ +struct Foo<|>; + +mod marker { + trait Copy {} +} +"#, + ); + } +} -- cgit v1.2.3