]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/goto_declaration.rs
Merge #11579
[rust.git] / crates / ide / src / goto_declaration.rs
1 use hir::Semantics;
2 use ide_db::{
3     defs::{Definition, NameClass, NameRefClass},
4     RootDatabase,
5 };
6 use syntax::{ast, match_ast, AstNode, SyntaxKind::*, T};
7
8 use crate::{FilePosition, NavigationTarget, RangeInfo};
9
10 // Feature: Go to Declaration
11 //
12 // Navigates to the declaration of an identifier.
13 pub(crate) fn goto_declaration(
14     db: &RootDatabase,
15     position: FilePosition,
16 ) -> Option<RangeInfo<Vec<NavigationTarget>>> {
17     let sema = Semantics::new(db);
18     let file = sema.parse(position.file_id).syntax().clone();
19     let original_token = file
20         .token_at_offset(position.offset)
21         .find(|it| matches!(it.kind(), IDENT | T![self] | T![super] | T![crate]))?;
22     let range = original_token.text_range();
23     let info: Vec<NavigationTarget> = sema
24         .descend_into_macros(original_token)
25         .iter()
26         .filter_map(|token| {
27             let parent = token.parent()?;
28             let def = match_ast! {
29                 match parent {
30                     ast::NameRef(name_ref) => match NameRefClass::classify(&sema, &name_ref)? {
31                         NameRefClass::Definition(it) => Some(it),
32                         _ => None
33                     },
34                     ast::Name(name) => match NameClass::classify(&sema, &name)? {
35                         NameClass::Definition(it) => Some(it),
36                         _ => None
37                     },
38                     _ => None
39                 }
40             };
41             match def? {
42                 Definition::Module(module) => {
43                     Some(NavigationTarget::from_module_to_decl(db, module))
44                 }
45                 _ => None,
46             }
47         })
48         .collect();
49
50     Some(RangeInfo::new(range, info))
51 }
52
53 #[cfg(test)]
54 mod tests {
55     use ide_db::base_db::FileRange;
56     use itertools::Itertools;
57
58     use crate::fixture;
59
60     fn check(ra_fixture: &str) {
61         let (analysis, position, expected) = fixture::annotations(ra_fixture);
62         let navs = analysis
63             .goto_declaration(position)
64             .unwrap()
65             .expect("no declaration or definition found")
66             .info;
67         if navs.is_empty() {
68             panic!("unresolved reference")
69         }
70
71         let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
72         let navs = navs
73             .into_iter()
74             .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
75             .sorted_by_key(cmp)
76             .collect::<Vec<_>>();
77         let expected = expected
78             .into_iter()
79             .map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
80             .sorted_by_key(cmp)
81             .collect::<Vec<_>>();
82         assert_eq!(expected, navs);
83     }
84
85     #[test]
86     fn goto_decl_module_outline() {
87         check(
88             r#"
89 //- /main.rs
90 mod foo;
91  // ^^^
92 //- /foo.rs
93 use self$0;
94 "#,
95         )
96     }
97
98     #[test]
99     fn goto_decl_module_inline() {
100         check(
101             r#"
102 mod foo {
103  // ^^^
104     use self$0;
105 }
106 "#,
107         )
108     }
109 }