1 //! This module generates [moniker](https://microsoft.github.io/language-server-protocol/specifications/lsif/0.6.0/specification/#exportsImports)
4 use hir::{db::DefDatabase, AsAssocItem, AssocItemContainer, Crate, Name, Semantics};
6 base_db::{CrateOrigin, FileId, FileLoader, FilePosition, LangCrateOrigin},
7 defs::{Definition, IdentClass},
8 helpers::pick_best_token,
11 use itertools::Itertools;
12 use syntax::{AstNode, SyntaxKind::*, T};
14 use crate::{doc_links::token_as_doc_comment, RangeInfo};
16 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17 pub struct MonikerIdentifier {
22 impl ToString for MonikerIdentifier {
23 fn to_string(&self) -> String {
25 MonikerIdentifier { path, crate_name } => {
26 format!("{}::{}", crate_name, path.iter().map(|x| x.to_string()).join("::"))
32 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
33 pub enum MonikerKind {
38 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
39 pub struct MonikerResult {
40 pub identifier: MonikerIdentifier,
41 pub kind: MonikerKind,
42 pub package_information: PackageInformation,
45 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
46 pub struct PackageInformation {
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());
64 pub(crate) fn moniker(
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 {
80 kind if kind.is_trivia() => 0,
83 if let Some(doc_comment) = token_as_doc_comment(&original_token) {
84 return doc_comment.get_definition_with_descend_at(sema, offset, |def, _, _| {
85 let m = def_to_moniker(db, def, current_crate)?;
86 Some(RangeInfo::new(original_token.text_range(), vec![m]))
90 .descend_into_macros(original_token.clone())
93 IdentClass::classify_token(sema, &token).map(IdentClass::definitions).map(|it| {
94 it.into_iter().flat_map(|def| def_to_moniker(sema.db, def, current_crate))
100 Some(RangeInfo::new(original_token.text_range(), navs))
103 pub(crate) fn def_to_moniker(
107 ) -> Option<MonikerResult> {
108 if matches!(def, Definition::GenericParam(_) | Definition::SelfType(_) | Definition::Local(_)) {
111 let module = def.module(db)?;
112 let krate = module.krate();
113 let mut path = vec![];
114 path.extend(module.path_to_root(db).into_iter().filter_map(|x| x.name(db)));
116 // Handle associated items within a trait
117 if let Some(assoc) = def.as_assoc_item(db) {
118 let container = assoc.container(db);
120 AssocItemContainer::Trait(trait_) => {
121 // Because different traits can have functions with the same name,
122 // we have to include the trait name as part of the moniker for uniqueness.
123 path.push(trait_.name(db));
125 AssocItemContainer::Impl(impl_) => {
126 // Because a struct can implement multiple traits, for implementations
127 // we add both the struct name and the trait name to the path
128 if let Some(adt) = impl_.self_ty(db).as_adt() {
129 path.push(adt.name(db));
132 if let Some(trait_) = impl_.trait_(db) {
133 path.push(trait_.name(db));
139 if let Definition::Field(it) = def {
140 path.push(it.parent_def(db).name(db));
143 path.push(def.name(db)?);
145 identifier: MonikerIdentifier {
146 crate_name: krate.display_name(db)?.crate_name().to_string(),
149 kind: if krate == from_crate { MonikerKind::Export } else { MonikerKind::Import },
150 package_information: {
151 let name = krate.display_name(db)?.to_string();
152 let (repo, version) = match krate.origin(db) {
153 CrateOrigin::CratesIo { repo } => (repo?, krate.version(db)?),
154 CrateOrigin::Lang(lang) => (
155 "https://github.com/rust-lang/rust/".to_string(),
157 LangCrateOrigin::Other => {
158 "https://github.com/rust-lang/rust/library/".into()
160 lang => format!("https://github.com/rust-lang/rust/library/{lang}",),
164 PackageInformation { name, repo, version }
173 use super::MonikerKind;
176 fn no_moniker(ra_fixture: &str) {
177 let (analysis, position) = fixture::position(ra_fixture);
178 if let Some(x) = analysis.moniker(position).unwrap() {
179 assert_eq!(x.info.len(), 0, "Moniker founded but no moniker expected: {:?}", x);
184 fn check_moniker(ra_fixture: &str, identifier: &str, package: &str, kind: MonikerKind) {
185 let (analysis, position) = fixture::position(ra_fixture);
186 let x = analysis.moniker(position).unwrap().expect("no moniker found").info;
187 assert_eq!(x.len(), 1);
188 let x = x.into_iter().next().unwrap();
189 assert_eq!(identifier, x.identifier.to_string());
190 assert_eq!(package, format!("{:?}", x.package_information));
191 assert_eq!(kind, x.kind);
198 //- /lib.rs crate:main deps:foo
199 use foo::module::func;
203 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
209 r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
214 //- /lib.rs crate:main deps:foo
215 use foo::module::func;
219 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
225 r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
231 fn moniker_for_trait() {
234 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
241 "foo::module::MyTrait::func",
242 r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
248 fn moniker_for_trait_constant() {
251 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
254 const MY_CONST$0: u8;
258 "foo::module::MyTrait::MY_CONST",
259 r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
265 fn moniker_for_trait_type() {
268 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
275 "foo::module::MyTrait::MyType",
276 r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
282 fn moniker_for_trait_impl_function() {
285 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
293 impl MyTrait for MyStruct {
298 "foo::module::MyStruct::MyTrait::func",
299 r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
305 fn moniker_for_field() {
308 //- /lib.rs crate:main deps:foo
311 let x = St { a$0: 2 };
313 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
319 r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
325 fn no_moniker_for_local() {
328 //- /lib.rs crate:main deps:foo
329 use foo::module::func;
333 //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git