]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide/src/hover.rs
internal: retire famous_defs_fixture
[rust.git] / crates / ide / src / hover.rs
index 5f9edb4761339c31070d50f7ce1370491f54f43f..409f81ca09f6669f546ef372b73bfaab2d96128f 100644 (file)
@@ -1,23 +1,29 @@
 use either::Either;
 use hir::{
-    AsAssocItem, AssocItemContainer, GenericParam, HasAttrs, HasSource, HirDisplay, Module,
+    AsAssocItem, AssocItemContainer, GenericParam, HasAttrs, HasSource, HirDisplay, InFile, Module,
     ModuleDef, Semantics,
 };
 use ide_db::{
     base_db::SourceDatabase,
     defs::{Definition, NameClass, NameRefClass},
-    helpers::FamousDefs,
+    helpers::{
+        generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
+        FamousDefs,
+    },
     RootDatabase,
 };
 use itertools::Itertools;
 use stdx::format_to;
-use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
+use syntax::{
+    algo, ast, match_ast, AstNode, AstToken, Direction, SyntaxKind::*, SyntaxToken, TokenAtOffset,
+    T,
+};
 
 use crate::{
     display::{macro_label, TryToNav},
     doc_links::{
-        doc_owner_to_def, extract_positioned_link_from_comment, remove_links,
-        resolve_doc_path_for_def, rewrite_links,
+        doc_attributes, extract_definitions_from_markdown, remove_links, resolve_doc_path_for_def,
+        rewrite_links,
     },
     markdown_remove::remove_markdown,
     markup::Markup,
@@ -28,6 +34,7 @@
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct HoverConfig {
     pub implementations: bool,
+    pub references: bool,
     pub run: bool,
     pub debug: bool,
     pub goto_type_def: bool,
@@ -38,6 +45,7 @@ pub struct HoverConfig {
 impl HoverConfig {
     pub const NO_ACTIONS: Self = Self {
         implementations: false,
+        references: false,
         run: false,
         debug: false,
         goto_type_def: false,
@@ -46,7 +54,7 @@ impl HoverConfig {
     };
 
     pub fn any(&self) -> bool {
-        self.implementations || self.runnable() || self.goto_type_def
+        self.implementations || self.references || self.runnable() || self.goto_type_def
     }
 
     pub fn none(&self) -> bool {
@@ -62,6 +70,7 @@ pub fn runnable(&self) -> bool {
 pub enum HoverAction {
     Runnable(Runnable),
     Implementation(FilePosition),
+    Reference(FilePosition),
     GoToType(Vec<HoverGotoTypeData>),
 }
 
@@ -115,15 +124,28 @@ pub(crate) fn hover(
                 |d| d.defined(db),
             ),
 
-            _ => ast::Comment::cast(token.clone())
-                .and_then(|comment| {
+            _ => {
+                if ast::Comment::cast(token.clone()).is_some() {
+                    cov_mark::hit!(no_highlight_on_comment_hover);
+                    let (attributes, def) = doc_attributes(&sema, &node)?;
+                    let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
                     let (idl_range, link, ns) =
-                        extract_positioned_link_from_comment(position.offset, &comment)?;
+                        extract_definitions_from_markdown(docs.as_str()).into_iter().find_map(|(range, link, ns)| {
+                            let InFile { file_id, value: range } = doc_mapping.map(range)?;
+                            if file_id == position.file_id.into() && range.contains(position.offset) {
+                                Some((range, link, ns))
+                            } else {
+                                None
+                            }
+                        })?;
                     range = Some(idl_range);
-                    let def = doc_owner_to_def(&sema, &node)?;
-                    resolve_doc_path_for_def(db, def, &link, ns)
-                })
-                .map(Definition::ModuleDef),
+                    resolve_doc_path_for_def(db, def, &link, ns).map(Definition::ModuleDef)
+                } else if let res@Some(_) = try_hover_for_attribute(&token) {
+                    return res;
+                } else {
+                    None
+                }
+            },
         }
     };
 
@@ -140,6 +162,10 @@ pub(crate) fn hover(
                 res.actions.push(action);
             }
 
+            if let Some(action) = show_fn_references_action(db, definition) {
+                res.actions.push(action);
+            }
+
             if let Some(action) = runnable_action(&sema, definition, position.file_id) {
                 res.actions.push(action);
             }
@@ -153,11 +179,6 @@ pub(crate) fn hover(
         }
     }
 
-    if token.kind() == syntax::SyntaxKind::COMMENT {
-        cov_mark::hit!(no_highlight_on_comment_hover);
-        return None;
-    }
-
     if let res @ Some(_) = hover_for_keyword(&sema, links_in_hover, markdown, &token) {
         return res;
     }
@@ -186,6 +207,51 @@ pub(crate) fn hover(
     Some(RangeInfo::new(range, res))
 }
 
+fn try_hover_for_attribute(token: &SyntaxToken) -> Option<RangeInfo<HoverResult>> {
+    let attr = token.ancestors().find_map(ast::Attr::cast)?;
+    let (path, tt) = attr.as_simple_call()?;
+    if !tt.syntax().text_range().contains(token.text_range().start()) {
+        return None;
+    }
+    let (is_clippy, lints) = match &*path {
+        "feature" => (false, FEATURES),
+        "allow" | "deny" | "forbid" | "warn" => {
+            let is_clippy = algo::non_trivia_sibling(token.clone().into(), Direction::Prev)
+                .filter(|t| t.kind() == T![:])
+                .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
+                .filter(|t| t.kind() == T![:])
+                .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
+                .map_or(false, |t| {
+                    t.kind() == T![ident] && t.into_token().map_or(false, |t| t.text() == "clippy")
+                });
+            if is_clippy {
+                (true, CLIPPY_LINTS)
+            } else {
+                (false, DEFAULT_LINTS)
+            }
+        }
+        _ => return None,
+    };
+
+    let tmp;
+    let needle = if is_clippy {
+        tmp = format!("clippy::{}", token.text());
+        &tmp
+    } else {
+        &*token.text()
+    };
+
+    let lint =
+        lints.binary_search_by_key(&needle, |lint| lint.label).ok().map(|idx| &lints[idx])?;
+    Some(RangeInfo::new(
+        token.text_range(),
+        HoverResult {
+            markup: Markup::from(format!("```\n{}\n```\n___\n\n{}", lint.label, lint.description)),
+            ..Default::default()
+        },
+    ))
+}
+
 fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
     fn to_action(nav_target: NavigationTarget) -> HoverAction {
         HoverAction::Implementation(FilePosition {
@@ -203,6 +269,18 @@ fn to_action(nav_target: NavigationTarget) -> HoverAction {
     adt.try_to_nav(db).map(to_action)
 }
 
+fn show_fn_references_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
+    match def {
+        Definition::ModuleDef(ModuleDef::Function(it)) => it.try_to_nav(db).map(|nav_target| {
+            HoverAction::Reference(FilePosition {
+                file_id: nav_target.file_id,
+                offset: nav_target.focus_or_full_range().start(),
+            })
+        }),
+        _ => None,
+    }
+}
+
 fn runnable_action(
     sema: &Semantics<RootDatabase>,
     def: Definition,
@@ -210,7 +288,7 @@ fn runnable_action(
 ) -> Option<HoverAction> {
     match def {
         Definition::ModuleDef(it) => match it {
-            ModuleDef::Module(it) => runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it)),
+            ModuleDef::Module(it) => runnable_mod(sema, it).map(HoverAction::Runnable),
             ModuleDef::Function(func) => {
                 let src = func.source(sema.db)?;
                 if src.file_id != file_id.into() {
@@ -219,7 +297,7 @@ fn runnable_action(
                     return None;
                 }
 
-                runnable_fn(&sema, func).map(HoverAction::Runnable)
+                runnable_fn(sema, func).map(HoverAction::Runnable)
             }
             _ => None,
         },
@@ -354,7 +432,7 @@ fn hover_for_definition(
     return match def {
         Definition::Macro(it) => match &it.source(db)?.value {
             Either::Left(mac) => {
-                let label = macro_label(&mac);
+                let label = macro_label(mac);
                 from_def_source_labeled(db, it, Some(label), mod_path)
             }
             Either::Right(_) => {
@@ -438,7 +516,7 @@ fn hover_for_keyword(
     if !token.kind().is_keyword() {
         return None;
     }
-    let famous_defs = FamousDefs(&sema, sema.scope(&token.parent()?).krate());
+    let famous_defs = FamousDefs(sema, sema.scope(&token.parent()?).krate());
     // std exposes {}_keyword modules with docstrings on the root to document keywords
     let keyword_mod = format!("{}_keyword", token.text());
     let doc_owner = find_std_module(&famous_defs, &keyword_mod)?;
@@ -490,8 +568,6 @@ mod tests {
 
     use crate::fixture;
 
-    use super::*;
-
     fn check_hover_no_result(ra_fixture: &str) {
         let (analysis, position) = fixture::position(ra_fixture);
         assert!(analysis.hover(position, true, true).unwrap().is_none());
@@ -1743,9 +1819,10 @@ pub struct Bar
         );
     }
 
-    #[ignore = "path based links currently only support documentation on ModuleDef items"]
     #[test]
     fn test_hover_path_link_field() {
+        // FIXME: Should be
+        //  [Foo](https://docs.rs/test/*/test/struct.Foo.html)
         check(
             r#"
 pub struct Foo;
@@ -1767,7 +1844,7 @@ pub struct Bar {
 
                 ---
 
-                [Foo](https://docs.rs/test/*/test/struct.Foo.html)
+                [Foo](struct.Foo.html)
             "#]],
         );
     }
@@ -2369,6 +2446,14 @@ fn foo_$0test() {}
 "#,
             expect![[r#"
                 [
+                    Reference(
+                        FilePosition {
+                            file_id: FileId(
+                                0,
+                            ),
+                            offset: 11,
+                        },
+                    ),
                     Runnable(
                         Runnable {
                             nav: NavigationTarget {
@@ -2913,29 +2998,24 @@ fn foo(ar$0g: &impl Foo + Bar<S>) {}
     fn test_hover_async_block_impl_trait_has_goto_type_action() {
         check_actions(
             r#"
+//- minicore: future
 struct S;
 fn foo() {
     let fo$0o = async { S };
 }
-
-#[prelude_import] use future::*;
-mod future {
-    #[lang = "future_trait"]
-    pub trait Future { type Output; }
-}
 "#,
             expect![[r#"
                 [
                     GoToType(
                         [
                             HoverGotoTypeData {
-                                mod_path: "test::future::Future",
+                                mod_path: "core::future::Future",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        0,
+                                        1,
                                     ),
-                                    full_range: 101..163,
-                                    focus_range: 140..146,
+                                    full_range: 248..430,
+                                    focus_range: 287..293,
                                     name: "Future",
                                     kind: Trait,
                                     description: "pub trait Future",
@@ -3731,11 +3811,14 @@ mod bar
 
     #[test]
     fn hover_keyword() {
-        let ra_fixture = r#"//- /main.rs crate:main deps:std
-fn f() { retur$0n; }"#;
-        let fixture = format!("{}\n{}", ra_fixture, FamousDefs::FIXTURE);
         check(
-            &fixture,
+            r#"
+//- /main.rs crate:main deps:std
+fn f() { retur$0n; }
+//- /libstd.rs crate:std
+/// Docs for return_keyword
+mod return_keyword {}
+"#,
             expect![[r#"
                 *return*
 
@@ -3752,11 +3835,15 @@ fn hover_keyword() {
 
     #[test]
     fn hover_builtin() {
-        let ra_fixture = r#"//- /main.rs crate:main deps:std
-cosnt _: &str$0 = ""; }"#;
-        let fixture = format!("{}\n{}", ra_fixture, FamousDefs::FIXTURE);
         check(
-            &fixture,
+            r#"
+//- /main.rs crate:main deps:std
+cosnt _: &str$0 = ""; }
+
+//- /libstd.rs crate:std
+/// Docs for prim_str
+mod prim_str {}
+"#,
             expect![[r#"
                 *str*
 
@@ -3814,24 +3901,229 @@ fn bar<'t, T>(s: &mut S<'t, T>, t: u32) -> *mut u32
     fn hover_intra_doc_links() {
         check(
             r#"
-/// This is the [`foo`](foo$0) function.
-fn foo() {}
+
+pub mod theitem {
+    /// This is the item. Cool!
+    pub struct TheItem;
+}
+
+/// Gives you a [`TheItem$0`].
+///
+/// [`TheItem`]: theitem::TheItem
+pub fn gimme() -> theitem::TheItem {
+    theitem::TheItem
+}
 "#,
             expect![[r#"
-                *[`foo`](foo)*
+                *[`TheItem`]*
 
                 ```rust
-                test
+                test::theitem
                 ```
 
                 ```rust
-                fn foo()
+                pub struct TheItem
                 ```
 
                 ---
 
-                This is the [`foo`](https://docs.rs/test/*/test/fn.foo.html) function.
+                This is the item. Cool!
+            "#]],
+        );
+    }
+
+    #[test]
+    fn hover_generic_assoc() {
+        check(
+            r#"
+fn foo<T: A>() where T::Assoc$0: {}
+
+trait A {
+    type Assoc;
+}"#,
+            expect![[r#"
+                *Assoc*
+
+                ```rust
+                test
+                ```
+
+                ```rust
+                type Assoc
+                ```
+            "#]],
+        );
+        check(
+            r#"
+fn foo<T: A>() {
+    let _: <T>::Assoc$0;
+}
+
+trait A {
+    type Assoc;
+}"#,
+            expect![[r#"
+                *Assoc*
+
+                ```rust
+                test
+                ```
+
+                ```rust
+                type Assoc
+                ```
+            "#]],
+        );
+        check(
+            r#"
+trait A where
+    Self::Assoc$0: ,
+{
+    type Assoc;
+}"#,
+            expect![[r#"
+                *Assoc*
+
+                ```rust
+                test
+                ```
+
+                ```rust
+                type Assoc
+                ```
             "#]],
         );
     }
+
+    #[test]
+    fn string_shadowed_with_inner_items() {
+        check(
+            r#"
+//- /main.rs crate:main deps:alloc
+
+/// Custom `String` type.
+struct String;
+
+fn f() {
+    let _: String$0;
+
+    fn inner() {}
+}
+
+//- /alloc.rs crate:alloc
+#[prelude_import]
+pub use string::*;
+
+mod string {
+    /// This is `alloc::String`.
+    pub struct String;
+}
+            "#,
+            expect![[r#"
+                *String*
+
+                ```rust
+                main
+                ```
+
+                ```rust
+                struct String
+                ```
+
+                ---
+
+                Custom `String` type.
+            "#]],
+        )
+    }
+
+    #[test]
+    fn function_doesnt_shadow_crate_in_use_tree() {
+        check(
+            r#"
+//- /main.rs crate:main deps:foo
+use foo$0::{foo};
+
+//- /foo.rs crate:foo
+pub fn foo() {}
+"#,
+            expect![[r#"
+                *foo*
+
+                ```rust
+                extern crate foo
+                ```
+            "#]],
+        )
+    }
+
+    #[test]
+    fn hover_feature() {
+        check(
+            r#"#![feature(box_syntax$0)]"#,
+            expect![[r##"
+                *box_syntax*
+                ```
+                box_syntax
+                ```
+                ___
+
+                # `box_syntax`
+
+                The tracking issue for this feature is: [#49733]
+
+                [#49733]: https://github.com/rust-lang/rust/issues/49733
+
+                See also [`box_patterns`](box-patterns.md)
+
+                ------------------------
+
+                Currently the only stable way to create a `Box` is via the `Box::new` method.
+                Also it is not possible in stable Rust to destructure a `Box` in a match
+                pattern. The unstable `box` keyword can be used to create a `Box`. An example
+                usage would be:
+
+                ```rust
+                #![feature(box_syntax)]
+
+                fn main() {
+                    let b = box 5;
+                }
+                ```
+
+            "##]],
+        )
+    }
+
+    #[test]
+    fn hover_lint() {
+        check(
+            r#"#![allow(arithmetic_overflow$0)]"#,
+            expect![[r#"
+                *arithmetic_overflow*
+                ```
+                arithmetic_overflow
+                ```
+                ___
+
+                arithmetic operation overflows
+            "#]],
+        )
+    }
+
+    #[test]
+    fn hover_clippy_lint() {
+        check(
+            r#"#![allow(clippy::almost_swapped$0)]"#,
+            expect![[r#"
+                *almost_swapped*
+                ```
+                clippy::almost_swapped
+                ```
+                ___
+
+                Checks for `foo = bar; bar = foo` sequences.
+            "#]],
+        )
+    }
 }