]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/moniker.rs
fixup: properly handle all associated items
[rust.git] / crates / ide / src / moniker.rs
1 //! This module generates [moniker](https://microsoft.github.io/language-server-protocol/specifications/lsif/0.6.0/specification/#exportsImports)
2 //! for LSIF and LSP.
3
4 use hir::{db::DefDatabase, AsAssocItem, AssocItemContainer, Crate, Name, Semantics};
5 use ide_db::{
6     base_db::{CrateOrigin, FileId, FileLoader, FilePosition},
7     defs::Definition,
8     helpers::pick_best_token,
9     RootDatabase,
10 };
11 use itertools::Itertools;
12 use syntax::{AstNode, SyntaxKind::*, T};
13
14 use crate::{doc_links::token_as_doc_comment, RangeInfo};
15
16 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17 pub struct MonikerIdentifier {
18     crate_name: String,
19     path: Vec<Name>,
20 }
21
22 impl ToString for MonikerIdentifier {
23     fn to_string(&self) -> String {
24         match self {
25             MonikerIdentifier { path, crate_name } => {
26                 format!("{}::{}", crate_name, path.iter().map(|x| x.to_string()).join("::"))
27             }
28         }
29     }
30 }
31
32 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
33 pub enum MonikerKind {
34     Import,
35     Export,
36 }
37
38 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
39 pub struct MonikerResult {
40     pub identifier: MonikerIdentifier,
41     pub kind: MonikerKind,
42     pub package_information: PackageInformation,
43 }
44
45 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
46 pub struct PackageInformation {
47     pub name: String,
48     pub repo: String,
49     pub version: String,
50 }
51
52 pub(crate) fn crate_for_file(db: &RootDatabase, file_id: FileId) -> Option<Crate> {
53     for &krate in db.relevant_crates(file_id).iter() {
54         let crate_def_map = db.crate_def_map(krate);
55         for (_, data) in crate_def_map.modules() {
56             if data.origin.file_id() == Some(file_id) {
57                 return Some(krate.into());
58             }
59         }
60     }
61     None
62 }
63
64 pub(crate) fn moniker(
65     db: &RootDatabase,
66     FilePosition { file_id, offset }: FilePosition,
67 ) -> Option<RangeInfo<Vec<MonikerResult>>> {
68     let sema = &Semantics::new(db);
69     let file = sema.parse(file_id).syntax().clone();
70     let current_crate = crate_for_file(db, file_id)?;
71     let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
72         IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | COMMENT => 2,
73         kind if kind.is_trivia() => 0,
74         _ => 1,
75     })?;
76     if let Some(doc_comment) = token_as_doc_comment(&original_token) {
77         return doc_comment.get_definition_with_descend_at(sema, offset, |def, _, _| {
78             let m = def_to_moniker(db, def, current_crate)?;
79             Some(RangeInfo::new(original_token.text_range(), vec![m]))
80         });
81     }
82     let navs = sema
83         .descend_into_macros(original_token.clone())
84         .into_iter()
85         .map(|token| {
86             Definition::from_token(sema, &token)
87                 .into_iter()
88                 .flat_map(|def| def_to_moniker(sema.db, def, current_crate))
89                 .collect::<Vec<_>>()
90         })
91         .flatten()
92         .unique()
93         .collect::<Vec<_>>();
94     Some(RangeInfo::new(original_token.text_range(), navs))
95 }
96
97 pub(crate) fn def_to_moniker(
98     db: &RootDatabase,
99     def: Definition,
100     from_crate: Crate,
101 ) -> Option<MonikerResult> {
102     if matches!(def, Definition::GenericParam(_) | Definition::SelfType(_) | Definition::Local(_)) {
103         return None;
104     }
105     let module = def.module(db)?;
106     let krate = module.krate();
107     let mut path = vec![];
108     path.extend(module.path_to_root(db).into_iter().filter_map(|x| x.name(db)));
109
110     // Handle associated items within a trait
111     if let Some(assoc) = def.as_assoc_item(db) {
112         let container = assoc.container(db);
113         if let AssocItemContainer::Trait(parent_trait) = container {
114             path.push(parent_trait.name(db));
115         }
116     }
117
118     if let Definition::Field(it) = def {
119         path.push(it.parent_def(db).name(db));
120     }
121
122     path.push(def.name(db)?);
123     Some(MonikerResult {
124         identifier: MonikerIdentifier {
125             crate_name: krate.display_name(db)?.crate_name().to_string(),
126             path,
127         },
128         kind: if krate == from_crate { MonikerKind::Export } else { MonikerKind::Import },
129         package_information: {
130             let name = krate.display_name(db)?.to_string();
131             let (repo, version) = match krate.origin(db) {
132                 CrateOrigin::CratesIo { repo } => (repo?, krate.version(db)?),
133                 CrateOrigin::Lang => (
134                     "https://github.com/rust-lang/rust/".to_string(),
135                     "compiler_version".to_string(),
136                 ),
137                 CrateOrigin::Unknown => return None,
138             };
139             PackageInformation { name, repo, version }
140         },
141     })
142 }
143
144 #[cfg(test)]
145 mod tests {
146     use crate::fixture;
147
148     use super::MonikerKind;
149
150     #[track_caller]
151     fn no_moniker(ra_fixture: &str) {
152         let (analysis, position) = fixture::position(ra_fixture);
153         if let Some(x) = analysis.moniker(position).unwrap() {
154             assert_eq!(x.info.len(), 0, "Moniker founded but no moniker expected: {:?}", x);
155         }
156     }
157
158     #[track_caller]
159     fn check_moniker(ra_fixture: &str, identifier: &str, package: &str, kind: MonikerKind) {
160         let (analysis, position) = fixture::position(ra_fixture);
161         let x = analysis.moniker(position).unwrap().expect("no moniker found").info;
162         assert_eq!(x.len(), 1);
163         let x = x.into_iter().next().unwrap();
164         assert_eq!(identifier, x.identifier.to_string());
165         assert_eq!(package, format!("{:?}", x.package_information));
166         assert_eq!(kind, x.kind);
167     }
168
169     #[test]
170     fn basic() {
171         check_moniker(
172             r#"
173 //- /lib.rs crate:main deps:foo
174 use foo::module::func;
175 fn main() {
176     func$0();
177 }
178 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
179 pub mod module {
180     pub fn func() {}
181 }
182 "#,
183             "foo::module::func",
184             r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
185             MonikerKind::Import,
186         );
187         check_moniker(
188             r#"
189 //- /lib.rs crate:main deps:foo
190 use foo::module::func;
191 fn main() {
192     func();
193 }
194 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
195 pub mod module {
196     pub fn func$0() {}
197 }
198 "#,
199             "foo::module::func",
200             r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
201             MonikerKind::Export,
202         );
203     }
204
205     #[test]
206     fn moniker_for_trait() {
207         check_moniker(
208             r#"
209 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
210 pub mod module {
211     pub trait MyTrait {
212         pub fn func$0() {}
213     }
214 }
215 "#,
216             "foo::module::MyTrait::func",
217             r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
218             MonikerKind::Export,
219         );
220     }
221
222     #[test]
223     fn moniker_for_trait_constant() {
224         check_moniker(
225             r#"
226 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
227 pub mod module {
228     pub trait MyTrait {
229         const MY_CONST$0: u8;
230     }
231 }
232 "#,
233             "foo::module::MyTrait::MY_CONST",
234             r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
235             MonikerKind::Export,
236         );
237     }
238
239     #[test]
240     fn moniker_for_trait_type() {
241         check_moniker(
242             r#"
243 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
244 pub mod module {
245     pub trait MyTrait {
246         type MyType$0;
247     }
248 }
249 "#,
250             "foo::module::MyTrait::MyType",
251             r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
252             MonikerKind::Export,
253         );
254     }
255
256     #[test]
257     fn moniker_for_field() {
258         check_moniker(
259             r#"
260 //- /lib.rs crate:main deps:foo
261 use foo::St;
262 fn main() {
263     let x = St { a$0: 2 };
264 }
265 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
266 pub struct St {
267     pub a: i32,
268 }
269 "#,
270             "foo::St::a",
271             r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
272             MonikerKind::Import,
273         );
274     }
275
276     #[test]
277     fn no_moniker_for_local() {
278         no_moniker(
279             r#"
280 //- /lib.rs crate:main deps:foo
281 use foo::module::func;
282 fn main() {
283     func();
284 }
285 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
286 pub mod module {
287     pub fn func() {
288         let x$0 = 2;
289     }
290 }
291 "#,
292         );
293     }
294 }