use expect_test::{Expect, expect};
use ide_db::{FileRange, MiniCore, base_db::SourceDatabase};
use syntax::TextRange;
use crate::{
HoverConfig, HoverDocFormat, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, fixture,
};
use hir::setup_tracing;
const HOVER_BASE_CONFIG: HoverConfig<'_> = HoverConfig {
links_in_hover: false,
memory_layout: Some(MemoryLayoutHoverConfig {
size: Some(MemoryLayoutHoverRenderKind::Both),
offset: Some(MemoryLayoutHoverRenderKind::Both),
alignment: Some(MemoryLayoutHoverRenderKind::Both),
padding: Some(MemoryLayoutHoverRenderKind::Both),
niches: true,
}),
documentation: true,
format: HoverDocFormat::Markdown,
keywords: true,
max_trait_assoc_items_count: None,
max_fields_count: Some(5),
max_enum_variants_count: Some(5),
max_subst_ty_len: super::SubstTyLen::Unlimited,
show_drop_glue: true,
minicore: MiniCore::default(),
};
fn check_hover_no_result(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis
.hover(
&HoverConfig { links_in_hover: true, ..HOVER_BASE_CONFIG },
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap();
assert!(hover.is_none(), "hover not expected but found: {:?}", hover.unwrap());
}
#[track_caller]
fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
let _tracing = setup_tracing();
let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis
.hover(
&HoverConfig { links_in_hover: true, ..HOVER_BASE_CONFIG },
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap()
.unwrap();
let content = analysis.db.file_text(position.file_id);
let hovered_element = &content.text(&analysis.db)[hover.range];
let actual = format!("*{hovered_element}*\n{}\n", hover.info.markup);
expect.assert_eq(&actual)
}
#[track_caller]
fn check_hover_fields_limit(
fields_count: impl Into<Option<usize>>,
#[rust_analyzer::rust_fixture] ra_fixture: &str,
expect: Expect,
) {
let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis
.hover(
&HoverConfig {
links_in_hover: true,
max_fields_count: fields_count.into(),
..HOVER_BASE_CONFIG
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap()
.unwrap();
let content = analysis.db.file_text(position.file_id).text(&analysis.db);
let hovered_element = &content[hover.range];
let actual = format!("*{hovered_element}*\n{}\n", hover.info.markup);
expect.assert_eq(&actual)
}
#[track_caller]
fn check_hover_enum_variants_limit(
variants_count: impl Into<Option<usize>>,
#[rust_analyzer::rust_fixture] ra_fixture: &str,
expect: Expect,
) {
let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis
.hover(
&HoverConfig {
links_in_hover: true,
max_enum_variants_count: variants_count.into(),
..HOVER_BASE_CONFIG
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap()
.unwrap();
let content = analysis.db.file_text(position.file_id).text(&analysis.db);
let hovered_element = &content[hover.range];
let actual = format!("*{hovered_element}*\n{}\n", hover.info.markup);
expect.assert_eq(&actual)
}
#[track_caller]
fn check_assoc_count(
count: usize,
#[rust_analyzer::rust_fixture] ra_fixture: &str,
expect: Expect,
) {
let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis
.hover(
&HoverConfig {
links_in_hover: true,
max_trait_assoc_items_count: Some(count),
..HOVER_BASE_CONFIG
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap()
.unwrap();
let content = analysis.db.file_text(position.file_id).text(&analysis.db);
let hovered_element = &content[hover.range];
let actual = format!("*{hovered_element}*\n{}\n", hover.info.markup);
expect.assert_eq(&actual)
}
fn check_hover_no_links(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis
.hover(
&HOVER_BASE_CONFIG,
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap()
.unwrap();
let content = analysis.db.file_text(position.file_id).text(&analysis.db);
let hovered_element = &content[hover.range];
let actual = format!("*{hovered_element}*\n{}\n", hover.info.markup);
expect.assert_eq(&actual)
}
fn check_hover_no_memory_layout(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis
.hover(
&HoverConfig { memory_layout: None, ..HOVER_BASE_CONFIG },
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap()
.unwrap();
let content = analysis.db.file_text(position.file_id).text(&analysis.db);
let hovered_element = &content[hover.range];
let actual = format!("*{hovered_element}*\n{}\n", hover.info.markup);
expect.assert_eq(&actual)
}
fn check_hover_no_markdown(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis
.hover(
&HoverConfig {
links_in_hover: true,
format: HoverDocFormat::PlainText,
..HOVER_BASE_CONFIG
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap()
.unwrap();
let content = analysis.db.file_text(position.file_id).text(&analysis.db);
let hovered_element = &content[hover.range];
let actual = format!("*{hovered_element}*\n{}\n", hover.info.markup);
expect.assert_eq(&actual)
}
fn check_actions(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
let (analysis, file_id, position) = fixture::range_or_position(ra_fixture);
let mut hover = analysis
.hover(
&HoverConfig { links_in_hover: true, ..HOVER_BASE_CONFIG },
FileRange { file_id, range: position.range_or_empty() },
)
.unwrap()
.unwrap();
// stub out ranges into minicore as they can change every now and then
hover.info.actions.iter_mut().for_each(|action| match action {
super::HoverAction::GoToType(act) => act.iter_mut().for_each(|data| {
if data.nav.file_id == file_id {
return;
}
data.nav.full_range = TextRange::empty(span::TextSize::new(!0));
if let Some(range) = &mut data.nav.focus_range {
*range = TextRange::empty(span::TextSize::new(!0));
}
}),
_ => (),
});
expect.assert_debug_eq(&hover.info.actions)
}
fn check_hover_range(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
let (analysis, range) = fixture::range(ra_fixture);
let hover = analysis.hover(&HOVER_BASE_CONFIG, range).unwrap().unwrap();
expect.assert_eq(hover.info.markup.as_str())
}
fn check_hover_range_actions(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
let (analysis, range) = fixture::range(ra_fixture);
let mut hover = analysis
.hover(&HoverConfig { links_in_hover: true, ..HOVER_BASE_CONFIG }, range)
.unwrap()
.unwrap();
// stub out ranges into minicore as they can change every now and then
hover.info.actions.iter_mut().for_each(|action| match action {
super::HoverAction::GoToType(act) => act.iter_mut().for_each(|data| {
if data.nav.file_id == range.file_id {
return;
}
data.nav.full_range = TextRange::empty(span::TextSize::new(!0));
if let Some(range) = &mut data.nav.focus_range {
*range = TextRange::empty(span::TextSize::new(!0));
}
}),
_ => (),
});
expect.assert_debug_eq(&hover.info.actions);
}
fn check_hover_range_no_results(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
let (analysis, range) = fixture::range(ra_fixture);
let hover = analysis.hover(&HOVER_BASE_CONFIG, range).unwrap();
assert!(hover.is_none());
}
#[test]
fn hover_descend_macros_avoids_duplicates() {
check(
r#"
macro_rules! dupe_use {
($local:ident) => {
{
$local;
$local;
}
}
}
fn foo() {
let local = 0;
dupe_use!(local$0);
}
"#,
expect![[r#"
*local*
```rust
let local: i32
```
"#]],
);
}
#[test]
fn hover_shows_all_macro_descends() {
check(
r#"
macro_rules! m {
($name:ident) => {
/// Outer
fn $name() {}
mod module {
/// Inner
fn $name() {}
}
};
}
m!(ab$0c);
"#,
expect![[r#"
*abc*
```rust
ra_test_fixture
```
```rust
fn abc()
```
---
Outer
---
```rust
ra_test_fixture::module
```
```rust
fn abc()
```
---
Inner
"#]],
);
}
#[test]
fn hover_remove_markdown_if_configured() {
check_hover_no_markdown(
r#"
pub fn foo() -> u32 { 1 }
fn main() {
let foo_test = foo$0();
}
"#,
expect![[r#"
*foo*
ra_test_fixture
pub fn foo() -> u32
"#]],
);
}
#[test]
fn hover_closure() {
check(
r#"
//- minicore: copy, add, builtin_impls
fn main() {
let x = 2;
let y = $0|z| x + z;
}
"#,
expect![[r#"
*|*
```rust
impl Fn(i32) -> i32
```
---
size = 8, align = 8, niches = 1
## Captures
* `x` by immutable borrow
"#]],
);
check(
r#"
//- minicore: copy
fn foo(x: impl Fn(i32) -> i32) {
}
fn main() {
foo($0|x: i32| x)
}
"#,
expect![[r#"
*|*
```rust
impl Fn(i32) -> i32
```
---
size = 0, align = 1
## Captures
This closure captures nothing
"#]],
);
check(
r#"
//- minicore: copy
struct Z { f: i32 }
struct Y(&'static mut Z)
struct X {
f1: Y,
f2: (Y, Y),
}
fn main() {
let x: X;
let y = $0|| {
x.f1;
&mut x.f2.0 .0.f;
};
}
"#,
expect![[r#"
*|*
```rust
impl FnOnce()
```
---
size = 16 (0x10), align = 8, niches = 1
## Captures
* `x.f1` by move
* `(*x.f2.0.0).f` by mutable borrow
"#]],
);
check(
r#"
//- minicore: copy, option
fn do_char(c: char) {}
fn main() {
let x = None;
let y = |$0| {
match x {
Some(c) => do_char(c),
None => x = None,
}
};
}
"#,
expect![[r#"
*|*
```rust
impl FnMut()
```
---
size = 8, align = 8, niches = 1
## Captures
* `x` by mutable borrow
"#]],
);
}
#[test]
fn hover_ranged_closure() {
check_hover_range(
r#"
//- minicore: fn
struct S;
struct S2;
fn main() {
let x = &S;
let y = ($0|| {x; S2}$0).call();
}
"#,
expect![[r#"
```rust
impl FnOnce() -> S2
```
---
size = 8, align = 8, niches = 1
Coerced to: &impl FnOnce() -> S2
## Captures
* `x` by move"#]],
);
check_hover_range_actions(
r#"
//- minicore: fn
struct S;
struct S2;
fn main() {
let x = &S;
let y = ($0|| {x; S2}$0).call();
}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "ra_test_fixture::S2",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 10..20,
focus_range: 17..19,
name: "S2",
kind: Struct,
description: "struct S2",
},
},
HoverGotoTypeData {
mod_path: "ra_test_fixture::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..9,
focus_range: 7..8,
name: "S",
kind: Struct,
description: "struct S",
},
},
HoverGotoTypeData {
mod_path: "core::ops::function::FnOnce",
nav: NavigationTarget {
file_id: FileId(
1,
),
full_range: 4294967295..4294967295,
focus_range: 4294967295..4294967295,
name: "FnOnce",
kind: Trait,
contain