+use either::Either;
use hir::{
- Adt, AsAssocItem, AssocItemContainer, FieldSource, GenericParam, HasAttrs, HasSource,
- HirDisplay, Module, ModuleDef, ModuleSource, Semantics,
+ 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, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
-use test_utils::mark;
+use syntax::{
+ algo, ast, match_ast, AstNode, AstToken, Direction, SyntaxKind::*, SyntaxToken, TokenAtOffset,
+ T,
+};
use crate::{
- display::{macro_label, ShortLabel, TryToNav},
- doc_links::{remove_links, rewrite_links},
+ display::{macro_label, TryToNav},
+ doc_links::{
+ doc_attributes, extract_definitions_from_markdown, remove_links, resolve_doc_path_for_def,
+ rewrite_links,
+ },
markdown_remove::remove_markdown,
markup::Markup,
runnables::{runnable_fn, runnable_mod},
#[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,
impl HoverConfig {
pub const NO_ACTIONS: Self = Self {
implementations: false,
+ references: false,
run: false,
debug: false,
goto_type_def: false,
};
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 {
pub enum HoverAction {
Runnable(Runnable),
Implementation(FilePosition),
+ Reference(FilePosition),
GoToType(Vec<HoverGotoTypeData>),
}
//
// Shows additional information, like type of an expression or documentation for definition when "focusing" code.
// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
+//
+// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
pub(crate) fn hover(
db: &RootDatabase,
position: FilePosition,
let mut res = HoverResult::default();
- let node = token.parent();
+ let node = token.parent()?;
+ let mut range = None;
let definition = match_ast! {
match node {
// we don't use NameClass::referenced_or_defined here as we do not want to resolve
// field pattern shorthands to their definition
ast::Name(name) => NameClass::classify(&sema, &name).and_then(|class| match class {
NameClass::ConstReference(def) => Some(def),
- def => def.defined(sema.db),
+ def => def.defined(db),
}),
- ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)),
- ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime)
- .map_or_else(|| NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(sema.db)), |d| d.defined(sema.db)),
- _ => None,
+ ast::NameRef(name_ref) => {
+ NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(db))
+ },
+ ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime).map_or_else(
+ || NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(db)),
+ |d| d.defined(db),
+ ),
+
+ _ => {
+ 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_definitions_from_markdown(docs.as_str()).into_iter().find_map(|(range, link, ns)| {
+ let InFile { file_id, value: range } = doc_mapping.map(range.clone())?;
+ if file_id == position.file_id.into() && range.contains(position.offset) {
+ Some((range, link, ns))
+ } else {
+ None
+ }
+ })?;
+ range = Some(idl_range);
+ 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
+ }
+ },
}
};
+
if let Some(definition) = definition {
let famous_defs = match &definition {
Definition::ModuleDef(ModuleDef::BuiltinType(_)) => {
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);
}
res.actions.push(action);
}
- let range = sema.original_range(&node).range;
+ let range = range.unwrap_or_else(|| sema.original_range(&node).range);
return Some(RangeInfo::new(range, res));
}
}
- if token.kind() == syntax::SyntaxKind::COMMENT {
- // don't highlight the entire parent node on comment hover
- return None;
- }
if let res @ Some(_) = hover_for_keyword(&sema, links_in_hover, markdown, &token) {
return res;
}
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 {
let adt = match def {
Definition::ModuleDef(ModuleDef::Trait(it)) => return it.try_to_nav(db).map(to_action),
Definition::ModuleDef(ModuleDef::Adt(it)) => Some(it),
- Definition::SelfType(it) => it.target_ty(db).as_adt(),
+ Definition::SelfType(it) => it.self_ty(db).as_adt(),
_ => None,
}?;
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,
) -> 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(|it| HoverAction::Runnable(it)),
ModuleDef::Function(func) => {
let src = func.source(sema.db)?;
if src.file_id != file_id.into() {
- mark::hit!(hover_macro_generated_struct_fn_doc_comment);
- mark::hit!(hover_macro_generated_struct_fn_doc_attr);
+ cov_mark::hit!(hover_macro_generated_struct_fn_doc_comment);
+ cov_mark::hit!(hover_macro_generated_struct_fn_doc_attr);
return None;
}
- runnable_fn(&sema, func).map(HoverAction::Runnable)
+ runnable_fn(sema, func).map(HoverAction::Runnable)
}
_ => None,
},
Definition::ModuleDef(md) => match md {
ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) {
AssocItemContainer::Trait(t) => Some(t.name(db)),
- AssocItemContainer::Impl(i) => i.target_ty(db).as_adt().map(|adt| adt.name(db)),
+ AssocItemContainer::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)),
},
ModuleDef::Variant(e) => Some(e.parent_enum(db).name(db)),
_ => None,
) -> Option<Markup> {
let mod_path = definition_mod_path(db, &def);
return match def {
- Definition::Macro(it) => {
- let label = macro_label(&it.source(db)?.value);
- from_def_source_labeled(db, it, Some(label), mod_path)
- }
- Definition::Field(def) => {
- let src = def.source(db)?.value;
- if let FieldSource::Named(it) = src {
- from_def_source_labeled(db, def, it.short_label(), mod_path)
- } else {
+ Definition::Macro(it) => match &it.source(db)?.value {
+ Either::Left(mac) => {
+ let label = macro_label(mac);
+ from_def_source_labeled(db, it, Some(label), mod_path)
+ }
+ Either::Right(_) => {
+ // FIXME
None
}
- }
+ },
+ Definition::Field(def) => from_hir_fmt(db, def, mod_path),
Definition::ModuleDef(it) => match it {
- ModuleDef::Module(it) => from_def_source_labeled(
- db,
- it,
- match it.definition_source(db).value {
- ModuleSource::Module(it) => it.short_label(),
- ModuleSource::SourceFile(it) => it.short_label(),
- ModuleSource::BlockExpr(it) => it.short_label(),
- },
- mod_path,
- ),
- ModuleDef::Function(it) => from_def_source(db, it, mod_path),
- ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it, mod_path),
- ModuleDef::Adt(Adt::Union(it)) => from_def_source(db, it, mod_path),
- ModuleDef::Adt(Adt::Enum(it)) => from_def_source(db, it, mod_path),
- ModuleDef::Variant(it) => from_def_source(db, it, mod_path),
- ModuleDef::Const(it) => from_def_source(db, it, mod_path),
- ModuleDef::Static(it) => from_def_source(db, it, mod_path),
- ModuleDef::Trait(it) => from_def_source(db, it, mod_path),
- ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path),
+ ModuleDef::Module(it) => from_hir_fmt(db, it, mod_path),
+ ModuleDef::Function(it) => from_hir_fmt(db, it, mod_path),
+ ModuleDef::Adt(it) => from_hir_fmt(db, it, mod_path),
+ ModuleDef::Variant(it) => from_hir_fmt(db, it, mod_path),
+ ModuleDef::Const(it) => from_hir_fmt(db, it, mod_path),
+ ModuleDef::Static(it) => from_hir_fmt(db, it, mod_path),
+ ModuleDef::Trait(it) => from_hir_fmt(db, it, mod_path),
+ ModuleDef::TypeAlias(it) => from_hir_fmt(db, it, mod_path),
ModuleDef::BuiltinType(it) => famous_defs
.and_then(|fd| hover_for_builtin(fd, it))
.or_else(|| Some(Markup::fenced_block(&it.name()))),
},
- Definition::Local(it) => Some(Markup::fenced_block(&it.ty(db).display(db))),
+ Definition::Local(it) => hover_for_local(it, db),
Definition::SelfType(impl_def) => {
- impl_def.target_ty(db).as_adt().and_then(|adt| match adt {
- Adt::Struct(it) => from_def_source(db, it, mod_path),
- Adt::Union(it) => from_def_source(db, it, mod_path),
- Adt::Enum(it) => from_def_source(db, it, mod_path),
- })
+ impl_def.self_ty(db).as_adt().and_then(|adt| from_hir_fmt(db, adt, mod_path))
}
+ Definition::GenericParam(it) => from_hir_fmt(db, it, None),
Definition::Label(it) => Some(Markup::fenced_block(&it.name(db))),
- Definition::GenericParam(it) => match it {
- GenericParam::TypeParam(it) => Some(Markup::fenced_block(&it.display(db))),
- GenericParam::LifetimeParam(it) => Some(Markup::fenced_block(&it.name(db))),
- GenericParam::ConstParam(it) => from_def_source(db, it, None),
- },
};
- fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<Markup>
+ fn from_hir_fmt<D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<Markup>
where
- D: HasSource<Ast = A> + HasAttrs + Copy,
- A: ShortLabel,
+ D: HasAttrs + HirDisplay,
{
- let short_label = def.source(db)?.value.short_label();
- from_def_source_labeled(db, def, short_label, mod_path)
+ let label = def.display(db).to_string();
+ from_def_source_labeled(db, def, Some(label), mod_path)
}
fn from_def_source_labeled<D>(
}
}
+fn hover_for_local(it: hir::Local, db: &RootDatabase) -> Option<Markup> {
+ let ty = it.ty(db);
+ let ty = ty.display(db);
+ let is_mut = if it.is_mut(db) { "mut " } else { "" };
+ let desc = match it.source(db).value {
+ Either::Left(ident) => {
+ let name = it.name(db).unwrap();
+ let let_kw = if ident
+ .syntax()
+ .parent()
+ .map_or(false, |p| p.kind() == LET_STMT || p.kind() == CONDITION)
+ {
+ "let "
+ } else {
+ ""
+ };
+ format!("{}{}{}: {}", let_kw, is_mut, name, ty)
+ }
+ Either::Right(_) => format!("{}self: {}", is_mut, ty),
+ };
+ hover_markup(None, Some(desc), None)
+}
+
fn hover_for_keyword(
sema: &Semantics<RootDatabase>,
links_in_hover: bool,
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)?;
fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
return tokens.max_by_key(priority);
+
fn priority(n: &SyntaxToken) -> usize {
match n.kind() {
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
*iter*
```rust
- Iter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> Option<u32>, u32>>
+ let mut iter: Iter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> Option<u32>, u32>>
```
"#]],
);
```
```rust
- pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str
+ pub fn foo<'a, T>(b: &'a T) -> &'a str
+ where
+ T: AsRef<str>,
```
"#]],
);
```
```rust
- const foo: u32 = 123
+ const foo: u32
```
"#]],
);
*zz*
```rust
- Test<i32, u8>
+ let zz: Test<i32, u8>
```
"#]],
);
```
```rust
- Some
+ Some(T)
```
"#]],
);
*bar*
```rust
- Option<i32>
+ let bar: Option<i32>
```
"#]],
);
```
```rust
- Some
+ Some(T)
```
---
*foo*
```rust
- i32
+ foo: i32
```
"#]],
)
*foo*
```rust
- i32
+ foo: i32
```
"#]],
)
*foo*
```rust
- i32
+ foo: i32
```
"#]],
)
*foo*
```rust
- i32
+ foo: i32
```
"#]],
)
*_x*
```rust
- impl Deref<Target = u8> + DerefMut<Target = u8>
+ _x: impl Deref<Target = u8> + DerefMut<Target = u8>
```
"#]],
)
*foo_test*
```rust
- Thing
+ let foo_test: Thing
```
"#]],
)
```
```rust
- const C: u32 = 1
+ const C: u32
```
"#]],
)
*x*
```rust
- i32
+ let x: i32
```
"#]],
)
)
}
+ #[test]
+ fn test_hover_macro2_invocation() {
+ check(
+ r#"
+/// foo bar
+///
+/// foo bar baz
+macro foo() {}
+
+fn f() { fo$0o!(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ macro foo
+ ```
+
+ ---
+
+ foo bar
+
+ foo bar baz
+ "#]],
+ )
+ }
+
#[test]
fn test_hover_tuple_field() {
check(
*bar*
```rust
- u32
+ bar: u32
```
"#]],
);
*bar*
```rust
- u32
+ bar: u32
```
"#]],
);
```
"#]],
);
+ // Top level `pub(crate)` will be displayed as no visibility.
check(
- r#"pub(crate) async unsafe extern "C" fn foo$0() {}"#,
+ r#"mod m { pub(crate) async unsafe extern "C" fn foo$0() {} }"#,
expect![[r#"
*foo*
```rust
- test
+ test::m
```
```rust
//! abc123
"#,
expect![[r#"
- *std*
- Standard library for this test
+ *std*
+
+ ```rust
+ extern crate std
+ ```
+
+ ---
+
+ Standard library for this test
- Printed?
- abc123
+ Printed?
+ abc123
"#]],
);
check(
//! abc123
"#,
expect![[r#"
- *abc*
- Standard library for this test
+ *abc*
+
+ ```rust
+ extern crate std
+ ```
- Printed?
- abc123
+ ---
+
+ Standard library for this test
+
+ Printed?
+ abc123
"#]],
);
}
fn test_hover_struct_doc_comment() {
check(
r#"
-/// bar docs
+/// This is an example
+/// multiline doc
+///
+/// # Example
+///
+/// ```
+/// let five = 5;
+///
+/// assert_eq!(6, my_crate::add_one(5));
+/// ```
struct Bar;
fn foo() { let bar = Ba$0r; }
"#,
- expect![[r#"
+ expect![[r##"
*Bar*
```rust
---
- bar docs
- "#]],
+ This is an example
+ multiline doc
+
+ # Example
+
+ ```
+ let five = 5;
+
+ assert_eq!(6, my_crate::add_one(5));
+ ```
+ "##]],
);
}
```
```rust
- V
+ V { field: i32 }
```
---
#[test]
fn test_hover_macro_generated_struct_fn_doc_comment() {
- mark::check!(hover_macro_generated_struct_fn_doc_comment);
+ cov_mark::check!(hover_macro_generated_struct_fn_doc_comment);
check(
r#"
#[test]
fn test_hover_macro_generated_struct_fn_doc_attr() {
- mark::check!(hover_macro_generated_struct_fn_doc_attr);
+ cov_mark::check!(hover_macro_generated_struct_fn_doc_attr);
check(
r#"
"#,
expect![[r#"
[
+ Reference(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 11,
+ },
+ ),
Runnable(
Runnable {
nav: NavigationTarget {
focus_range: 24..25,
name: "S",
kind: Struct,
- description: "struct S",
+ description: "struct S<T>",
},
},
HoverGotoTypeData {
focus_range: 24..25,
name: "S",
kind: Struct,
- description: "struct S",
+ description: "struct S<T>",
},
},
HoverGotoTypeData {
focus_range: 6..9,
name: "Foo",
kind: Trait,
- description: "trait Foo",
+ description: "trait Foo<T>",
},
},
HoverGotoTypeData {
focus_range: 6..9,
name: "Foo",
kind: Trait,
- description: "trait Foo",
+ description: "trait Foo<T>",
},
},
HoverGotoTypeData {
focus_range: 22..25,
name: "Bar",
kind: Trait,
- description: "trait Bar",
+ description: "trait Bar<T>",
},
},
HoverGotoTypeData {
focus_range: 19..22,
name: "Bar",
kind: Trait,
- description: "trait Bar",
+ description: "trait Bar<T>",
},
},
HoverGotoTypeData {
focus_range: 6..9,
name: "Foo",
kind: Trait,
- description: "trait Foo",
+ description: "trait Foo<T>",
},
},
HoverGotoTypeData {
focus_range: 49..50,
name: "B",
kind: Struct,
- description: "struct B",
+ description: "struct B<T>",
},
},
HoverGotoTypeData {
focus_range: 6..9,
name: "Foo",
kind: Trait,
- description: "trait Foo",
+ description: "trait Foo<T>",
},
},
HoverGotoTypeData {
focus_range: 6..15,
name: "ImplTrait",
kind: Trait,
- description: "trait ImplTrait",
+ description: "trait ImplTrait<T>",
},
},
HoverGotoTypeData {
focus_range: 50..51,
name: "B",
kind: Struct,
- description: "struct B",
+ description: "struct B<T>",
},
},
HoverGotoTypeData {
focus_range: 28..36,
name: "DynTrait",
kind: Trait,
- description: "trait DynTrait",
+ description: "trait DynTrait<T>",
},
},
HoverGotoTypeData {
*f*
```rust
- &i32
+ f: &i32
```
"#]],
);
*self*
```rust
- &Foo
+ self: &Foo
```
"#]],
);
*self*
```rust
- Arc<Foo>
+ self: Arc<Foo>
```
"#]],
);
);
}
+ #[test]
+ fn hover_doc_block_style_indentend() {
+ check(
+ r#"
+/**
+ foo
+ ```rust
+ let x = 3;
+ ```
+*/
+fn foo$0() {}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo()
+ ```
+
+ ---
+
+ foo
+
+ ```rust
+ let x = 3;
+ ```
+ "#]],
+ );
+ }
+
#[test]
fn hover_comments_dont_highlight_parent() {
+ cov_mark::check!(no_highlight_on_comment_hover);
check_hover_no_result(
r#"
fn no_hover() {
```
```rust
- const FOO: usize = 3
+ const FOO: usize
```
---
"#,
expect![[r#"
*foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ mod foo
+ ```
+
+ ---
+
For the horde!
"#]],
);
```
```rust
- pub mod bar
+ mod bar
```
---
"#]],
);
}
+
+ #[test]
+ fn hover_macro_expanded_function() {
+ check(
+ r#"
+struct S<'a, T>(&'a T);
+trait Clone {}
+macro_rules! foo {
+ () => {
+ fn bar<'t, T: Clone + 't>(s: &mut S<'t, T>, t: u32) -> *mut u32 where
+ 't: 't + 't,
+ for<'a> T: Clone + 'a
+ { 0 as _ }
+ };
+}
+
+foo!();
+
+fn main() {
+ bar$0;
+}
+"#,
+ expect![[r#"
+ *bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn bar<'t, T>(s: &mut S<'t, T>, t: u32) -> *mut u32
+ where
+ T: Clone + 't,
+ 't: 't + 't,
+ for<'a> T: Clone + 'a,
+ ```
+ "#]],
+ )
+ }
+
+ #[test]
+ fn hover_intra_doc_links() {
+ check(
+ r#"
+
+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#"
+ *[`TheItem`]*
+
+ ```rust
+ test::theitem
+ ```
+
+ ```rust
+ pub struct TheItem
+ ```
+
+ ---
+
+ 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.
+ "#]],
+ )
+ }
}