From e60f3d265f74ba410ba52e10143fdbff56eaf342 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 23 Sep 2021 19:54:57 +0200 Subject: [PATCH] Split the hover module up --- crates/ide/src/display.rs | 3 - crates/ide/src/hover.rs | 4808 +---------------- crates/ide/src/hover/render.rs | 450 ++ crates/ide/src/hover/tests.rs | 4168 ++++++++++++++ crates/rust-analyzer/tests/slow-tests/tidy.rs | 2 +- 5 files changed, 4711 insertions(+), 4720 deletions(-) create mode 100644 crates/ide/src/hover/render.rs create mode 100644 crates/ide/src/hover/tests.rs diff --git a/crates/ide/src/display.rs b/crates/ide/src/display.rs index 71e97d6b48..474db2e8de 100644 --- a/crates/ide/src/display.rs +++ b/crates/ide/src/display.rs @@ -2,7 +2,4 @@ //! into types that may be used to render in a UI. pub(crate) mod navigation_target; - pub(crate) use navigation_target::{ToNav, TryToNav}; - -pub(crate) use syntax::display::macro_label; diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 4b7043bd9b..d9e990f029 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -1,27 +1,24 @@ +mod render; + +#[cfg(test)] +mod tests; + use std::iter; use either::Either; -use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo}; +use hir::{HasSource, Semantics}; use ide_db::{ - base_db::{FileRange, SourceDatabase}, + base_db::FileRange, defs::Definition, - helpers::{ - generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES}, - pick_best_token, FamousDefs, - }, + helpers::{pick_best_token, FamousDefs}, RootDatabase, }; use itertools::Itertools; -use stdx::format_to; -use syntax::{ - algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, Direction, SyntaxKind::*, - SyntaxNode, SyntaxToken, T, -}; +use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, T}; use crate::{ - display::{macro_label, TryToNav}, - doc_links::{remove_links, rewrite_links, token_as_doc_comment}, - markdown_remove::remove_markdown, + display::TryToNav, + doc_links::token_as_doc_comment, markup::Markup, runnables::{runnable_fn, runnable_mod}, FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, @@ -59,7 +56,7 @@ impl HoverAction { .into_iter() .filter_map(|it| { Some(HoverGotoTypeData { - mod_path: render_path( + mod_path: render::path( db, it.module(db)?, it.name(db).map(|name| name.to_string()), @@ -120,14 +117,13 @@ pub(crate) fn hover( } let descended = sema.descend_into_macros_many(original_token.clone()); - // attributes, require special machinery as they are mere ident tokens // FIXME: Definition should include known lints and the like instead of having this special case here - if let res @ Some(_) = descended.iter().find_map(|token| { + if let Some(res) = descended.iter().find_map(|token| { let attr = token.ancestors().find_map(ast::Attr::cast)?; - try_hover_for_lint(&attr, &token) + render::try_for_lint(&attr, &token) }) { - return res; + return Some(RangeInfo::new(original_token.text_range(), res)); } let result = descended @@ -147,17 +143,89 @@ pub(crate) fn hover( }); if result.is_none() { // fallbacks, show keywords or types - if let res @ Some(_) = hover_for_keyword(sema, config, &original_token) { - return res; + if let Some(res) = render::keyword(sema, config, &original_token) { + return Some(RangeInfo::new(original_token.text_range(), res)); } - if let res @ Some(_) = descended.iter().find_map(|token| type_hover(sema, config, token)) { + if let res @ Some(_) = + descended.iter().find_map(|token| hover_type_fallback(sema, config, token)) + { return res; } } result.map(|res| RangeInfo::new(original_token.text_range(), res)) } -fn type_hover( +pub(crate) fn hover_for_definition( + sema: &Semantics, + file_id: FileId, + definition: Definition, + node: &SyntaxNode, + config: &HoverConfig, +) -> Option { + let famous_defs = match &definition { + Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => { + Some(FamousDefs(&sema, sema.scope(&node).krate())) + } + _ => None, + }; + if let Some(markup) = render::definition(sema.db, definition, famous_defs.as_ref(), config) { + let mut res = HoverResult::default(); + res.markup = render::process_markup(sema.db, definition, &markup, config); + if let Some(action) = show_implementations_action(sema.db, definition) { + res.actions.push(action); + } + + if let Some(action) = show_fn_references_action(sema.db, definition) { + res.actions.push(action); + } + + if let Some(action) = runnable_action(&sema, definition, file_id) { + res.actions.push(action); + } + + if let Some(action) = goto_type_action_for_def(sema.db, definition) { + res.actions.push(action); + } + return Some(res); + } + None +} + +fn hover_ranged( + file: &SyntaxNode, + range: syntax::TextRange, + sema: &Semantics, + config: &HoverConfig, +) -> Option> { + let expr_or_pat = file.covering_element(range).ancestors().find_map(|it| { + match_ast! { + match it { + ast::Expr(expr) => Some(Either::Left(expr)), + ast::Pat(pat) => Some(Either::Right(pat)), + _ => None, + } + } + })?; + let res = match &expr_or_pat { + Either::Left(ast::Expr::TryExpr(try_expr)) => render::try_expr(sema, config, try_expr), + Either::Left(ast::Expr::PrefixExpr(prefix_expr)) + if prefix_expr.op_kind() == Some(ast::UnaryOp::Deref) => + { + render::deref_expr(sema, config, prefix_expr) + } + _ => None, + }; + let res = res.or_else(|| render::type_info(sema, config, &expr_or_pat)); + res.map(|it| { + let range = match expr_or_pat { + Either::Left(it) => it.syntax().text_range(), + Either::Right(it) => it.syntax().text_range(), + }; + RangeInfo::new(range, it) + }) +} + +fn hover_type_fallback( sema: &Semantics, config: &HoverConfig, token: &SyntaxToken, @@ -178,289 +246,11 @@ fn type_hover( } }; - let res = hover_type_info(&sema, config, &expr_or_pat)?; + let res = render::type_info(&sema, config, &expr_or_pat)?; let range = sema.original_range(&node).range; Some(RangeInfo::new(range, res)) } -fn hover_ranged( - file: &SyntaxNode, - range: syntax::TextRange, - sema: &Semantics, - config: &HoverConfig, -) -> Option> { - let expr_or_pat = file.covering_element(range).ancestors().find_map(|it| { - match_ast! { - match it { - ast::Expr(expr) => Some(Either::Left(expr)), - ast::Pat(pat) => Some(Either::Right(pat)), - _ => None, - } - } - })?; - let res = match &expr_or_pat { - Either::Left(ast::Expr::TryExpr(try_expr)) => hover_try_expr(sema, config, try_expr), - Either::Left(ast::Expr::PrefixExpr(prefix_expr)) - if prefix_expr.op_kind() == Some(ast::UnaryOp::Deref) => - { - hover_deref_expr(sema, config, prefix_expr) - } - _ => None, - }; - let res = res.or_else(|| hover_type_info(sema, config, &expr_or_pat)); - res.map(|it| { - let range = match expr_or_pat { - Either::Left(it) => it.syntax().text_range(), - Either::Right(it) => it.syntax().text_range(), - }; - RangeInfo::new(range, it) - }) -} - -fn hover_try_expr( - sema: &Semantics, - config: &HoverConfig, - try_expr: &ast::TryExpr, -) -> Option { - let inner_ty = sema.type_of_expr(&try_expr.expr()?)?.original; - let mut ancestors = try_expr.syntax().ancestors(); - let mut body_ty = loop { - let next = ancestors.next()?; - break match_ast! { - match next { - ast::Fn(fn_) => sema.to_def(&fn_)?.ret_type(sema.db), - ast::Item(__) => return None, - ast::ClosureExpr(closure) => sema.type_of_expr(&closure.body()?)?.original, - ast::EffectExpr(effect) => if matches!(effect.effect(), ast::Effect::Async(_) | ast::Effect::Try(_)| ast::Effect::Const(_)) { - sema.type_of_expr(&effect.block_expr()?.into())?.original - } else { - continue; - }, - _ => continue, - } - }; - }; - - if inner_ty == body_ty { - return None; - } - - let mut inner_ty = inner_ty; - let mut s = "Try Target".to_owned(); - - let adts = inner_ty.as_adt().zip(body_ty.as_adt()); - if let Some((hir::Adt::Enum(inner), hir::Adt::Enum(body))) = adts { - let famous_defs = FamousDefs(sema, sema.scope(&try_expr.syntax()).krate()); - // special case for two options, there is no value in showing them - if let Some(option_enum) = famous_defs.core_option_Option() { - if inner == option_enum && body == option_enum { - cov_mark::hit!(hover_try_expr_opt_opt); - return None; - } - } - - // special case two results to show the error variants only - if let Some(result_enum) = famous_defs.core_result_Result() { - if inner == result_enum && body == result_enum { - let error_type_args = - inner_ty.type_arguments().nth(1).zip(body_ty.type_arguments().nth(1)); - if let Some((inner, body)) = error_type_args { - inner_ty = inner; - body_ty = body; - s = "Try Error".to_owned(); - } - } - } - } - - let mut res = HoverResult::default(); - - let mut targets: Vec = Vec::new(); - let mut push_new_def = |item: hir::ModuleDef| { - if !targets.contains(&item) { - targets.push(item); - } - }; - walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def); - walk_and_push_ty(sema.db, &body_ty, &mut push_new_def); - res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets)); - - let inner_ty = inner_ty.display(sema.db).to_string(); - let body_ty = body_ty.display(sema.db).to_string(); - let ty_len_max = inner_ty.len().max(body_ty.len()); - - let l = "Propagated as: ".len() - " Type: ".len(); - let static_text_len_diff = l as isize - s.len() as isize; - let tpad = static_text_len_diff.max(0) as usize; - let ppad = static_text_len_diff.min(0).abs() as usize; - - res.markup = format!( - "{bt_start}{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n{bt_end}", - s, - inner_ty, - body_ty, - pad0 = ty_len_max + tpad, - pad1 = ty_len_max + ppad, - bt_start = if config.markdown() { "```text\n" } else { "" }, - bt_end = if config.markdown() { "```\n" } else { "" } - ) - .into(); - Some(res) -} - -fn hover_deref_expr( - sema: &Semantics, - config: &HoverConfig, - deref_expr: &ast::PrefixExpr, -) -> Option { - let inner_ty = sema.type_of_expr(&deref_expr.expr()?)?.original; - let TypeInfo { original, adjusted } = - sema.type_of_expr(&ast::Expr::from(deref_expr.clone()))?; - - let mut res = HoverResult::default(); - let mut targets: Vec = Vec::new(); - let mut push_new_def = |item: hir::ModuleDef| { - if !targets.contains(&item) { - targets.push(item); - } - }; - walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def); - walk_and_push_ty(sema.db, &original, &mut push_new_def); - - res.markup = if let Some(adjusted_ty) = adjusted { - walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def); - let original = original.display(sema.db).to_string(); - let adjusted = adjusted_ty.display(sema.db).to_string(); - let inner = inner_ty.display(sema.db).to_string(); - let type_len = "To type: ".len(); - let coerced_len = "Coerced to: ".len(); - let deref_len = "Dereferenced from: ".len(); - let max_len = (original.len() + type_len) - .max(adjusted.len() + coerced_len) - .max(inner.len() + deref_len); - format!( - "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}", - inner, - original, - adjusted, - ipad = max_len - deref_len, - apad = max_len - type_len, - opad = max_len - coerced_len, - bt_start = if config.markdown() { "```text\n" } else { "" }, - bt_end = if config.markdown() { "```\n" } else { "" } - ) - .into() - } else { - let original = original.display(sema.db).to_string(); - let inner = inner_ty.display(sema.db).to_string(); - let type_len = "To type: ".len(); - let deref_len = "Dereferenced from: ".len(); - let max_len = (original.len() + type_len).max(inner.len() + deref_len); - format!( - "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\n{bt_end}", - inner, - original, - ipad = max_len - deref_len, - apad = max_len - type_len, - bt_start = if config.markdown() { "```text\n" } else { "" }, - bt_end = if config.markdown() { "```\n" } else { "" } - ) - .into() - }; - res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets)); - - Some(res) -} - -fn hover_type_info( - sema: &Semantics, - config: &HoverConfig, - expr_or_pat: &Either, -) -> Option { - let TypeInfo { original, adjusted } = match expr_or_pat { - Either::Left(expr) => sema.type_of_expr(expr)?, - Either::Right(pat) => sema.type_of_pat(pat)?, - }; - - let mut res = HoverResult::default(); - let mut targets: Vec = Vec::new(); - let mut push_new_def = |item: hir::ModuleDef| { - if !targets.contains(&item) { - targets.push(item); - } - }; - walk_and_push_ty(sema.db, &original, &mut push_new_def); - - res.markup = if let Some(adjusted_ty) = adjusted { - walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def); - let original = original.display(sema.db).to_string(); - let adjusted = adjusted_ty.display(sema.db).to_string(); - let static_text_diff_len = "Coerced to: ".len() - "Type: ".len(); - format!( - "{bt_start}Type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}", - original, - adjusted, - apad = static_text_diff_len + adjusted.len().max(original.len()), - opad = original.len(), - bt_start = if config.markdown() { "```text\n" } else { "" }, - bt_end = if config.markdown() { "```\n" } else { "" } - ) - .into() - } else { - if config.markdown() { - Markup::fenced_block(&original.display(sema.db)) - } else { - original.display(sema.db).to_string().into() - } - }; - res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets)); - Some(res) -} - -fn try_hover_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option> { - 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 { fn to_action(nav_target: NavigationTarget) -> HoverAction { HoverAction::Implementation(FilePosition { @@ -559,4417 +349,3 @@ fn walk_and_push_ty( } }); } - -fn hover_markup(docs: Option, desc: String, mod_path: Option) -> Option { - let mut buf = String::new(); - - if let Some(mod_path) = mod_path { - if !mod_path.is_empty() { - format_to!(buf, "```rust\n{}\n```\n\n", mod_path); - } - } - format_to!(buf, "```rust\n{}\n```", desc); - - if let Some(doc) = docs { - format_to!(buf, "\n___\n\n{}", doc); - } - Some(buf.into()) -} - -fn process_markup( - db: &RootDatabase, - def: Definition, - markup: &Markup, - config: &HoverConfig, -) -> Markup { - let markup = markup.as_str(); - let markup = if !config.markdown() { - remove_markdown(markup) - } else if config.links_in_hover { - rewrite_links(db, markup, def) - } else { - remove_links(markup) - }; - Markup::from(markup) -} - -fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option { - match def { - Definition::Field(f) => Some(f.parent_def(db).name(db)), - Definition::Local(l) => l.parent(db).name(db), - Definition::ModuleDef(md) => match md { - hir::ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) { - hir::AssocItemContainer::Trait(t) => Some(t.name(db)), - hir::AssocItemContainer::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)), - }, - hir::ModuleDef::Variant(e) => Some(e.parent_enum(db).name(db)), - _ => None, - }, - _ => None, - } - .map(|name| name.to_string()) -} - -fn render_path(db: &RootDatabase, module: hir::Module, item_name: Option) -> String { - let crate_name = - db.crate_graph()[module.krate().into()].display_name.as_ref().map(|it| it.to_string()); - let module_path = module - .path_to_root(db) - .into_iter() - .rev() - .flat_map(|it| it.name(db).map(|name| name.to_string())); - crate_name.into_iter().chain(module_path).chain(item_name).join("::") -} - -fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option { - if let Definition::GenericParam(_) = def { - return None; - } - def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def))) -} - -pub(crate) fn hover_for_definition( - sema: &Semantics, - file_id: FileId, - definition: Definition, - node: &SyntaxNode, - config: &HoverConfig, -) -> Option { - let famous_defs = match &definition { - Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => { - Some(FamousDefs(&sema, sema.scope(&node).krate())) - } - _ => None, - }; - if let Some(markup) = markup_for_definition(sema.db, definition, famous_defs.as_ref(), config) { - let mut res = HoverResult::default(); - res.markup = process_markup(sema.db, definition, &markup, config); - if let Some(action) = show_implementations_action(sema.db, definition) { - res.actions.push(action); - } - - if let Some(action) = show_fn_references_action(sema.db, definition) { - res.actions.push(action); - } - - if let Some(action) = runnable_action(&sema, definition, file_id) { - res.actions.push(action); - } - - if let Some(action) = goto_type_action_for_def(sema.db, definition) { - res.actions.push(action); - } - return Some(res); - } - None -} - -fn markup_for_definition( - db: &RootDatabase, - def: Definition, - famous_defs: Option<&FamousDefs>, - config: &HoverConfig, -) -> Option { - let mod_path = definition_mod_path(db, &def); - let (label, docs) = match def { - Definition::Macro(it) => ( - match &it.source(db)?.value { - Either::Left(mac) => macro_label(mac), - Either::Right(mac_fn) => fn_as_proc_macro_label(mac_fn), - }, - it.attrs(db).docs(), - ), - Definition::Field(def) => label_and_docs(db, def), - Definition::ModuleDef(it) => match it { - hir::ModuleDef::Module(it) => label_and_docs(db, it), - hir::ModuleDef::Function(it) => label_and_docs(db, it), - hir::ModuleDef::Adt(it) => label_and_docs(db, it), - hir::ModuleDef::Variant(it) => label_and_docs(db, it), - hir::ModuleDef::Const(it) => label_and_docs(db, it), - hir::ModuleDef::Static(it) => label_and_docs(db, it), - hir::ModuleDef::Trait(it) => label_and_docs(db, it), - hir::ModuleDef::TypeAlias(it) => label_and_docs(db, it), - hir::ModuleDef::BuiltinType(it) => { - return famous_defs - .and_then(|fd| hover_for_builtin(fd, it)) - .or_else(|| Some(Markup::fenced_block(&it.name()))) - } - }, - Definition::Local(it) => return hover_for_local(it, db), - Definition::SelfType(impl_def) => { - impl_def.self_ty(db).as_adt().map(|adt| label_and_docs(db, adt))? - } - Definition::GenericParam(it) => label_and_docs(db, it), - Definition::Label(it) => return Some(Markup::fenced_block(&it.name(db))), - }; - - return hover_markup( - docs.filter(|_| config.documentation.is_some()).map(Into::into), - label, - mod_path, - ); - - fn label_and_docs(db: &RootDatabase, def: D) -> (String, Option) - where - D: HasAttrs + HirDisplay, - { - let label = def.display(db).to_string(); - let docs = def.attrs(db).docs(); - (label, docs) - } -} - -fn hover_for_local(it: hir::Local, db: &RootDatabase) -> Option { - 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, desc, None) -} - -fn hover_for_keyword( - sema: &Semantics, - config: &HoverConfig, - token: &SyntaxToken, -) -> Option> { - if !token.kind().is_keyword() || !config.documentation.is_some() { - return None; - } - 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)?; - let docs = doc_owner.attrs(sema.db).docs()?; - let markup = process_markup( - sema.db, - Definition::ModuleDef(doc_owner.into()), - &hover_markup(Some(docs.into()), token.text().into(), None)?, - config, - ); - Some(RangeInfo::new(token.text_range(), HoverResult { markup, actions: Default::default() })) -} - -fn hover_for_builtin(famous_defs: &FamousDefs, builtin: hir::BuiltinType) -> Option { - // std exposes prim_{} modules with docstrings on the root to document the builtins - let primitive_mod = format!("prim_{}", builtin.name()); - let doc_owner = find_std_module(famous_defs, &primitive_mod)?; - let docs = doc_owner.attrs(famous_defs.0.db).docs()?; - hover_markup(Some(docs.into()), builtin.name().to_string(), None) -} - -fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option { - let db = famous_defs.0.db; - let std_crate = famous_defs.std()?; - let std_root_module = std_crate.root_module(db); - std_root_module - .children(db) - .find(|module| module.name(db).map_or(false, |module| module.to_string() == name)) -} - -#[cfg(test)] -mod tests { - use expect_test::{expect, Expect}; - use ide_db::base_db::{FileLoader, FileRange}; - use syntax::TextRange; - - use crate::{fixture, hover::HoverDocFormat, HoverConfig}; - - fn check_hover_no_result(ra_fixture: &str) { - let (analysis, position) = fixture::position(ra_fixture); - let hover = analysis - .hover( - &HoverConfig { - links_in_hover: true, - documentation: Some(HoverDocFormat::Markdown), - }, - FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, - ) - .unwrap(); - assert!(hover.is_none(), "hover not expected but found: {:?}", hover.unwrap()); - } - - fn check(ra_fixture: &str, expect: Expect) { - let (analysis, position) = fixture::position(ra_fixture); - let hover = analysis - .hover( - &HoverConfig { - links_in_hover: true, - documentation: Some(HoverDocFormat::Markdown), - }, - 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[hover.range]; - - let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup); - expect.assert_eq(&actual) - } - - fn check_hover_no_links(ra_fixture: &str, expect: Expect) { - let (analysis, position) = fixture::position(ra_fixture); - let hover = analysis - .hover( - &HoverConfig { - links_in_hover: false, - documentation: Some(HoverDocFormat::Markdown), - }, - 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[hover.range]; - - let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup); - expect.assert_eq(&actual) - } - - fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) { - let (analysis, position) = fixture::position(ra_fixture); - let hover = analysis - .hover( - &HoverConfig { - links_in_hover: true, - documentation: Some(HoverDocFormat::PlainText), - }, - 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[hover.range]; - - let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup); - expect.assert_eq(&actual) - } - - fn check_actions(ra_fixture: &str, expect: Expect) { - let (analysis, file_id, position) = fixture::range_or_position(ra_fixture); - let hover = analysis - .hover( - &HoverConfig { - links_in_hover: true, - documentation: Some(HoverDocFormat::Markdown), - }, - FileRange { file_id, range: position.range_or_empty() }, - ) - .unwrap() - .unwrap(); - expect.assert_debug_eq(&hover.info.actions) - } - - fn check_hover_range(ra_fixture: &str, expect: Expect) { - let (analysis, range) = fixture::range(ra_fixture); - let hover = analysis - .hover( - &HoverConfig { - links_in_hover: false, - documentation: Some(HoverDocFormat::Markdown), - }, - range, - ) - .unwrap() - .unwrap(); - expect.assert_eq(hover.info.markup.as_str()) - } - - fn check_hover_range_no_results(ra_fixture: &str) { - let (analysis, range) = fixture::range(ra_fixture); - let hover = analysis - .hover( - &HoverConfig { - links_in_hover: false, - documentation: Some(HoverDocFormat::Markdown), - }, - 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 - test::module - ``` - - ```rust - fn abc() - ``` - - --- - - Inner - --- - - ```rust - test - ``` - - ```rust - fn abc() - ``` - - --- - - Outer - "#]], - ); - } - - #[test] - fn hover_shows_type_of_an_expression() { - check( - r#" -pub fn foo() -> u32 { 1 } - -fn main() { - let foo_test = foo()$0; -} -"#, - expect![[r#" - *foo()* - ```rust - u32 - ``` - "#]], - ); - } - - #[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()* - u32 - "#]], - ); - } - - #[test] - fn hover_shows_long_type_of_an_expression() { - check( - r#" -struct Scan { a: A, b: B, c: C } -struct Iter { inner: I } -enum Option { Some(T), None } - -struct OtherStruct { i: T } - -fn scan(a: A, b: B, c: C) -> Iter, B, C>> { - Iter { inner: Scan { a, b, c } } -} - -fn main() { - let num: i32 = 55; - let closure = |memo: &mut u32, value: &u32, _another: &mut u32| -> Option { - Option::Some(*memo + value) - }; - let number = 5u32; - let mut iter$0 = scan(OtherStruct { i: num }, closure, number); -} -"#, - expect![[r#" - *iter* - - ```rust - let mut iter: Iter>, |&mut u32, &u32, &mut u32| -> Option, u32>> - ``` - "#]], - ); - } - - #[test] - fn hover_shows_fn_signature() { - // Single file with result - check( - r#" -pub fn foo() -> u32 { 1 } - -fn main() { let foo_test = fo$0o(); } -"#, - expect![[r#" - *foo* - - ```rust - test - ``` - - ```rust - pub fn foo() -> u32 - ``` - "#]], - ); - - // Multiple candidates but results are ambiguous. - check( - r#" -//- /a.rs -pub fn foo() -> u32 { 1 } - -//- /b.rs -pub fn foo() -> &str { "" } - -//- /c.rs -pub fn foo(a: u32, b: u32) {} - -//- /main.rs -mod a; -mod b; -mod c; - -fn main() { let foo_test = fo$0o(); } - "#, - expect![[r#" - *foo* - ```rust - {unknown} - ``` - "#]], - ); - - // Use literal `crate` in path - check( - r#" -pub struct X; - -fn foo() -> crate::X { X } - -fn main() { f$0oo(); } - "#, - expect![[r#" - *foo* - - ```rust - test - ``` - - ```rust - fn foo() -> crate::X - ``` - "#]], - ); - - // Check `super` in path - check( - r#" -pub struct X; - -mod m { pub fn foo() -> super::X { super::X } } - -fn main() { m::f$0oo(); } - "#, - expect![[r#" - *foo* - - ```rust - test::m - ``` - - ```rust - pub fn foo() -> super::X - ``` - "#]], - ); - } - - #[test] - fn hover_shows_fn_signature_with_type_params() { - check( - r#" -pub fn foo<'a, T: AsRef>(b: &'a T) -> &'a str { } - -fn main() { let foo_test = fo$0o(); } - "#, - expect![[r#" - *foo* - - ```rust - test - ``` - - ```rust - pub fn foo<'a, T>(b: &'a T) -> &'a str - where - T: AsRef, - ``` - "#]], - ); - } - - #[test] - fn hover_shows_fn_signature_on_fn_name() { - check( - r#" -pub fn foo$0(a: u32, b: u32) -> u32 {} - -fn main() { } -"#, - expect![[r#" - *foo* - - ```rust - test - ``` - - ```rust - pub fn foo(a: u32, b: u32) -> u32 - ``` - "#]], - ); - } - - #[test] - fn hover_shows_fn_doc() { - check( - r#" -/// # Example -/// ``` -/// # use std::path::Path; -/// # -/// foo(Path::new("hello, world!")) -/// ``` -pub fn foo$0(_: &Path) {} - -fn main() { } -"#, - expect![[r##" - *foo* - - ```rust - test - ``` - - ```rust - pub fn foo(_: &Path) - ``` - - --- - - # Example - - ``` - # use std::path::Path; - # - foo(Path::new("hello, world!")) - ``` - "##]], - ); - } - - #[test] - fn hover_shows_fn_doc_attr_raw_string() { - check( - r##" -#[doc = r#"Raw string doc attr"#] -pub fn foo$0(_: &Path) {} - -fn main() { } -"##, - expect![[r##" - *foo* - - ```rust - test - ``` - - ```rust - pub fn foo(_: &Path) - ``` - - --- - - Raw string doc attr - "##]], - ); - } - - #[test] - fn hover_shows_struct_field_info() { - // Hovering over the field when instantiating - check( - r#" -struct Foo { field_a: u32 } - -fn main() { - let foo = Foo { field_a$0: 0, }; -} -"#, - expect![[r#" - *field_a* - - ```rust - test::Foo - ``` - - ```rust - field_a: u32 - ``` - "#]], - ); - - // Hovering over the field in the definition - check( - r#" -struct Foo { field_a$0: u32 } - -fn main() { - let foo = Foo { field_a: 0 }; -} -"#, - expect![[r#" - *field_a* - - ```rust - test::Foo - ``` - - ```rust - field_a: u32 - ``` - "#]], - ); - } - - #[test] - fn hover_const_static() { - check( - r#"const foo$0: u32 = 123;"#, - expect![[r#" - *foo* - - ```rust - test - ``` - - ```rust - const foo: u32 - ``` - "#]], - ); - check( - r#"static foo$0: u32 = 456;"#, - expect![[r#" - *foo* - - ```rust - test - ``` - - ```rust - static foo: u32 - ``` - "#]], - ); - } - - #[test] - fn hover_default_generic_types() { - check( - r#" -struct Test { k: K, t: T } - -fn main() { - let zz$0 = Test { t: 23u8, k: 33 }; -}"#, - expect![[r#" - *zz* - - ```rust - let zz: Test - ``` - "#]], - ); - } - - #[test] - fn hover_some() { - check( - r#" -enum Option { Some(T) } -use Option::Some; - -fn main() { So$0me(12); } -"#, - expect![[r#" - *Some* - - ```rust - test::Option - ``` - - ```rust - Some(T) - ``` - "#]], - ); - - check( - r#" -enum Option { Some(T) } -use Option::Some; - -fn main() { let b$0ar = Some(12); } -"#, - expect![[r#" - *bar* - - ```rust - let bar: Option - ``` - "#]], - ); - } - - #[test] - fn hover_enum_variant() { - check( - r#" -enum Option { - /// The None variant - Non$0e -} -"#, - expect![[r#" - *None* - - ```rust - test::Option - ``` - - ```rust - None - ``` - - --- - - The None variant - "#]], - ); - - check( - r#" -enum Option { - /// The Some variant - Some(T) -} -fn main() { - let s = Option::Som$0e(12); -} -"#, - expect![[r#" - *Some* - - ```rust - test::Option - ``` - - ```rust - Some(T) - ``` - - --- - - The Some variant - "#]], - ); - } - - #[test] - fn hover_for_local_variable() { - check( - r#"fn func(foo: i32) { fo$0o; }"#, - expect![[r#" - *foo* - - ```rust - foo: i32 - ``` - "#]], - ) - } - - #[test] - fn hover_for_local_variable_pat() { - check( - r#"fn func(fo$0o: i32) {}"#, - expect![[r#" - *foo* - - ```rust - foo: i32 - ``` - "#]], - ) - } - - #[test] - fn hover_local_var_edge() { - check( - r#"fn func(foo: i32) { if true { $0foo; }; }"#, - expect![[r#" - *foo* - - ```rust - foo: i32 - ``` - "#]], - ) - } - - #[test] - fn hover_for_param_edge() { - check( - r#"fn func($0foo: i32) {}"#, - expect![[r#" - *foo* - - ```rust - foo: i32 - ``` - "#]], - ) - } - - #[test] - fn hover_for_param_with_multiple_traits() { - check( - r#" - //- minicore: sized - trait Deref { - type Target: ?Sized; - } - trait DerefMut { - type Target: ?Sized; - } - fn f(_x$0: impl Deref + DerefMut) {}"#, - expect![[r#" - *_x* - - ```rust - _x: impl Deref + DerefMut - ``` - "#]], - ) - } - - #[test] - fn test_hover_infer_associated_method_result() { - check( - r#" -struct Thing { x: u32 } - -impl Thing { - fn new() -> Thing { Thing { x: 0 } } -} - -fn main() { let foo_$0test = Thing::new(); } -"#, - expect![[r#" - *foo_test* - - ```rust - let foo_test: Thing - ``` - "#]], - ) - } - - #[test] - fn test_hover_infer_associated_method_exact() { - check( - r#" -mod wrapper { - struct Thing { x: u32 } - - impl Thing { - fn new() -> Thing { Thing { x: 0 } } - } -} - -fn main() { let foo_test = wrapper::Thing::new$0(); } -"#, - expect![[r#" - *new* - - ```rust - test::wrapper::Thing - ``` - - ```rust - fn new() -> Thing - ``` - "#]], - ) - } - - #[test] - fn test_hover_infer_associated_const_in_pattern() { - check( - r#" -struct X; -impl X { - const C: u32 = 1; -} - -fn main() { - match 1 { - X::C$0 => {}, - 2 => {}, - _ => {} - }; -} -"#, - expect![[r#" - *C* - - ```rust - test - ``` - - ```rust - const C: u32 - ``` - "#]], - ) - } - - #[test] - fn test_hover_self() { - check( - r#" -struct Thing { x: u32 } -impl Thing { - fn new() -> Self { Self$0 { x: 0 } } -} -"#, - expect![[r#" - *Self* - - ```rust - test - ``` - - ```rust - struct Thing - ``` - "#]], - ); - check( - r#" -struct Thing { x: u32 } -impl Thing { - fn new() -> Self$0 { Self { x: 0 } } -} -"#, - expect![[r#" - *Self* - - ```rust - test - ``` - - ```rust - struct Thing - ``` - "#]], - ); - check( - r#" -enum Thing { A } -impl Thing { - pub fn new() -> Self$0 { Thing::A } -} -"#, - expect![[r#" - *Self* - - ```rust - test - ``` - - ```rust - enum Thing - ``` - "#]], - ); - check( - r#" - enum Thing { A } - impl Thing { - pub fn thing(a: Self$0) {} - } - "#, - expect![[r#" - *Self* - - ```rust - test - ``` - - ```rust - enum Thing - ``` - "#]], - ); - } - - #[test] - fn test_hover_shadowing_pat() { - check( - r#" -fn x() {} - -fn y() { - let x = 0i32; - x$0; -} -"#, - expect![[r#" - *x* - - ```rust - let x: i32 - ``` - "#]], - ) - } - - #[test] - fn test_hover_macro_invocation() { - check( - r#" -macro_rules! foo { () => {} } - -fn f() { fo$0o!(); } -"#, - expect![[r#" - *foo* - - ```rust - test - ``` - - ```rust - macro_rules! foo - ``` - "#]], - ) - } - - #[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( - r#"struct TS(String, i32$0);"#, - expect![[r#" - *i32* - - ```rust - i32 - ``` - "#]], - ) - } - - #[test] - fn test_hover_through_macro() { - check( - r#" -macro_rules! id { ($($tt:tt)*) => { $($tt)* } } -fn foo() {} -id! { - fn bar() { fo$0o(); } -} -"#, - expect![[r#" - *foo* - - ```rust - test - ``` - - ```rust - fn foo() - ``` - "#]], - ); - } - - #[test] - fn test_hover_through_attr() { - check( - r#" -//- proc_macros: identity -#[proc_macros::identity] -fn foo$0() {} -"#, - expect![[r#" - *foo* - - ```rust - test - ``` - - ```rust - fn foo() - ``` - "#]], - ); - } - - #[test] - fn test_hover_through_expr_in_macro() { - check( - r#" -macro_rules! id { ($($tt:tt)*) => { $($tt)* } } -fn foo(bar:u32) { let a = id!(ba$0r); } -"#, - expect![[r#" - *bar* - - ```rust - bar: u32 - ``` - "#]], - ); - } - - #[test] - fn test_hover_through_expr_in_macro_recursive() { - check( - r#" -macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } } -macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } } -fn foo(bar:u32) { let a = id!(ba$0r); } -"#, - expect![[r#" - *bar* - - ```rust - bar: u32 - ``` - "#]], - ); - } - - #[test] - fn test_hover_through_func_in_macro_recursive() { - check( - r#" -macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } } -macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } } -fn bar() -> u32 { 0 } -fn foo() { let a = id!([0u32, bar($0)] ); } -"#, - expect![[r#" - *bar()* - ```rust - u32 - ``` - "#]], - ); - } - - #[test] - fn test_hover_through_literal_string_in_macro() { - check( - r#" -macro_rules! arr { ($($tt:tt)*) => { [$($tt)*)] } } -fn foo() { - let mastered_for_itunes = ""; - let _ = arr!("Tr$0acks", &mastered_for_itunes); -} -"#, - expect![[r#" - *"Tracks"* - ```rust - &str - ``` - "#]], - ); - } - - #[test] - fn test_hover_through_assert_macro() { - check( - r#" -#[rustc_builtin_macro] -macro_rules! assert {} - -fn bar() -> bool { true } -fn foo() { - assert!(ba$0r()); -} -"#, - expect![[r#" - *bar* - - ```rust - test - ``` - - ```rust - fn bar() -> bool - ``` - "#]], - ); - } - - #[test] - fn test_hover_through_literal_string_in_builtin_macro() { - check_hover_no_result( - r#" - #[rustc_builtin_macro] - macro_rules! format {} - - fn foo() { - format!("hel$0lo {}", 0); - } -"#, - ); - } - - #[test] - fn test_hover_non_ascii_space_doc() { - check( - " -/// <- `\u{3000}` here -fn foo() { } - -fn bar() { fo$0o(); } -", - expect![[r#" - *foo* - - ```rust - test - ``` - - ```rust - fn foo() - ``` - - --- - - \<- ` ` here - "#]], - ); - } - - #[test] - fn test_hover_function_show_qualifiers() { - check( - r#"async fn foo$0() {}"#, - expect![[r#" - *foo* - - ```rust - test - ``` - - ```rust - async fn foo() - ``` - "#]], - ); - check( - r#"pub const unsafe fn foo$0() {}"#, - expect![[r#" - *foo* - - ```rust - test - ``` - - ```rust - pub const unsafe fn foo() - ``` - "#]], - ); - // Top level `pub(crate)` will be displayed as no visibility. - check( - r#"mod m { pub(crate) async unsafe extern "C" fn foo$0() {} }"#, - expect![[r#" - *foo* - - ```rust - test::m - ``` - - ```rust - pub(crate) async unsafe extern "C" fn foo() - ``` - "#]], - ); - } - - #[test] - fn test_hover_trait_show_qualifiers() { - check_actions( - r"unsafe trait foo$0() {}", - expect![[r#" - [ - Implementation( - FilePosition { - file_id: FileId( - 0, - ), - offset: 13, - }, - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_extern_crate() { - check( - r#" -//- /main.rs crate:main deps:std -extern crate st$0d; -//- /std/lib.rs crate:std -//! Standard library for this test -//! -//! Printed? -//! abc123 -"#, - expect![[r#" - *std* - - ```rust - extern crate std - ``` - - --- - - Standard library for this test - - Printed? - abc123 - "#]], - ); - check( - r#" -//- /main.rs crate:main deps:std -extern crate std as ab$0c; -//- /std/lib.rs crate:std -//! Standard library for this test -//! -//! Printed? -//! abc123 -"#, - expect![[r#" - *abc* - - ```rust - extern crate std - ``` - - --- - - Standard library for this test - - Printed? - abc123 - "#]], - ); - } - - #[test] - fn test_hover_mod_with_same_name_as_function() { - check( - r#" -use self::m$0y::Bar; -mod my { pub struct Bar; } - -fn my() {} -"#, - expect![[r#" - *my* - - ```rust - test - ``` - - ```rust - mod my - ``` - "#]], - ); - } - - #[test] - fn test_hover_struct_doc_comment() { - check( - r#" -/// 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##" - *Bar* - - ```rust - test - ``` - - ```rust - struct Bar - ``` - - --- - - This is an example - multiline doc - - # Example - - ``` - let five = 5; - - assert_eq!(6, my_crate::add_one(5)); - ``` - "##]], - ); - } - - #[test] - fn test_hover_struct_doc_attr() { - check( - r#" -#[doc = "bar docs"] -struct Bar; - -fn foo() { let bar = Ba$0r; } -"#, - expect![[r#" - *Bar* - - ```rust - test - ``` - - ```rust - struct Bar - ``` - - --- - - bar docs - "#]], - ); - } - - #[test] - fn test_hover_struct_doc_attr_multiple_and_mixed() { - check( - r#" -/// bar docs 0 -#[doc = "bar docs 1"] -#[doc = "bar docs 2"] -struct Bar; - -fn foo() { let bar = Ba$0r; } -"#, - expect![[r#" - *Bar* - - ```rust - test - ``` - - ```rust - struct Bar - ``` - - --- - - bar docs 0 - bar docs 1 - bar docs 2 - "#]], - ); - } - - #[test] - fn test_hover_external_url() { - check( - r#" -pub struct Foo; -/// [external](https://www.google.com) -pub struct B$0ar -"#, - expect![[r#" - *Bar* - - ```rust - test - ``` - - ```rust - pub struct Bar - ``` - - --- - - [external](https://www.google.com) - "#]], - ); - } - - // Check that we don't rewrite links which we can't identify - #[test] - fn test_hover_unknown_target() { - check( - r#" -pub struct Foo; -/// [baz](Baz) -pub struct B$0ar -"#, - expect![[r#" - *Bar* - - ```rust - test - ``` - - ```rust - pub struct Bar - ``` - - --- - - [baz](Baz) - "#]], - ); - } - - #[test] - fn test_hover_no_links() { - check_hover_no_links( - r#" -/// Test cases: -/// case 1. bare URL: https://www.example.com/ -/// case 2. inline URL with title: [example](https://www.example.com/) -/// case 3. code reference: [`Result`] -/// case 4. code reference but miss footnote: [`String`] -/// case 5. autolink: -/// case 6. email address: -/// case 7. reference: [example][example] -/// case 8. collapsed link: [example][] -/// case 9. shortcut link: [example] -/// case 10. inline without URL: [example]() -/// case 11. reference: [foo][foo] -/// case 12. reference: [foo][bar] -/// case 13. collapsed link: [foo][] -/// case 14. shortcut link: [foo] -/// case 15. inline without URL: [foo]() -/// case 16. just escaped text: \[foo] -/// case 17. inline link: [Foo](foo::Foo) -/// -/// [`Result`]: ../../std/result/enum.Result.html -/// [^example]: https://www.example.com/ -pub fn fo$0o() {} -"#, - expect![[r#" - *foo* - - ```rust - test - ``` - - ```rust - pub fn foo() - ``` - - --- - - Test cases: - case 1. bare URL: https://www.example.com/ - case 2. inline URL with title: [example](https://www.example.com/) - case 3. code reference: `Result` - case 4. code reference but miss footnote: `String` - case 5. autolink: http://www.example.com/ - case 6. email address: test@example.com - case 7. reference: example - case 8. collapsed link: example - case 9. shortcut link: example - case 10. inline without URL: example - case 11. reference: foo - case 12. reference: foo - case 13. collapsed link: foo - case 14. shortcut link: foo - case 15. inline without URL: foo - case 16. just escaped text: \[foo\] - case 17. inline link: Foo - - [^example]: https://www.example.com/ - "#]], - ); - } - - #[test] - fn test_hover_macro_generated_struct_fn_doc_comment() { - cov_mark::check!(hover_macro_generated_struct_fn_doc_comment); - - check( - r#" -macro_rules! bar { - () => { - struct Bar; - impl Bar { - /// Do the foo - fn foo(&self) {} - } - } -} - -bar!(); - -fn foo() { let bar = Bar; bar.fo$0o(); } -"#, - expect![[r#" - *foo* - - ```rust - test::Bar - ``` - - ```rust - fn foo(&self) - ``` - - --- - - Do the foo - "#]], - ); - } - - #[test] - fn test_hover_macro_generated_struct_fn_doc_attr() { - cov_mark::check!(hover_macro_generated_struct_fn_doc_attr); - - check( - r#" -macro_rules! bar { - () => { - struct Bar; - impl Bar { - #[doc = "Do the foo"] - fn foo(&self) {} - } - } -} - -bar!(); - -fn foo() { let bar = Bar; bar.fo$0o(); } -"#, - expect![[r#" - *foo* - - ```rust - test::Bar - ``` - - ```rust - fn foo(&self) - ``` - - --- - - Do the foo - "#]], - ); - } - - #[test] - fn test_hover_trait_has_impl_action() { - check_actions( - r#"trait foo$0() {}"#, - expect![[r#" - [ - Implementation( - FilePosition { - file_id: FileId( - 0, - ), - offset: 6, - }, - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_struct_has_impl_action() { - check_actions( - r"struct foo$0() {}", - expect![[r#" - [ - Implementation( - FilePosition { - file_id: FileId( - 0, - ), - offset: 7, - }, - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_union_has_impl_action() { - check_actions( - r#"union foo$0() {}"#, - expect![[r#" - [ - Implementation( - FilePosition { - file_id: FileId( - 0, - ), - offset: 6, - }, - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_enum_has_impl_action() { - check_actions( - r"enum foo$0() { A, B }", - expect![[r#" - [ - Implementation( - FilePosition { - file_id: FileId( - 0, - ), - offset: 5, - }, - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_self_has_impl_action() { - check_actions( - r#"struct foo where Self$0:;"#, - expect![[r#" - [ - Implementation( - FilePosition { - file_id: FileId( - 0, - ), - offset: 7, - }, - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_test_has_action() { - check_actions( - r#" -#[test] -fn foo_$0test() {} -"#, - expect![[r#" - [ - Reference( - FilePosition { - file_id: FileId( - 0, - ), - offset: 11, - }, - ), - Runnable( - Runnable { - use_name_in_title: false, - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..24, - focus_range: 11..19, - name: "foo_test", - kind: Function, - }, - kind: Test { - test_id: Path( - "foo_test", - ), - attr: TestAttr { - ignore: false, - }, - }, - cfg: None, - }, - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_test_mod_has_action() { - check_actions( - r#" -mod tests$0 { - #[test] - fn foo_test() {} -} -"#, - expect![[r#" - [ - Runnable( - Runnable { - use_name_in_title: false, - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..46, - focus_range: 4..9, - name: "tests", - kind: Module, - description: "mod tests", - }, - kind: TestMod { - path: "tests", - }, - cfg: None, - }, - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_struct_has_goto_type_action() { - check_actions( - r#" -struct S{ f1: u32 } - -fn main() { let s$0t = S{ f1:0 }; } -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::S", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..19, - focus_range: 7..8, - name: "S", - kind: Struct, - description: "struct S", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_generic_struct_has_goto_type_actions() { - check_actions( - r#" -struct Arg(u32); -struct S{ f1: T } - -fn main() { let s$0t = S{ f1:Arg(0) }; } -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::S", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 17..37, - focus_range: 24..25, - name: "S", - kind: Struct, - description: "struct S", - }, - }, - HoverGotoTypeData { - mod_path: "test::Arg", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..16, - focus_range: 7..10, - name: "Arg", - kind: Struct, - description: "struct Arg", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_generic_struct_has_flattened_goto_type_actions() { - check_actions( - r#" -struct Arg(u32); -struct S{ f1: T } - -fn main() { let s$0t = S{ f1: S{ f1: Arg(0) } }; } -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::S", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 17..37, - focus_range: 24..25, - name: "S", - kind: Struct, - description: "struct S", - }, - }, - HoverGotoTypeData { - mod_path: "test::Arg", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..16, - focus_range: 7..10, - name: "Arg", - kind: Struct, - description: "struct Arg", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_tuple_has_goto_type_actions() { - check_actions( - r#" -struct A(u32); -struct B(u32); -mod M { - pub struct C(u32); -} - -fn main() { let s$0t = (A(1), B(2), M::C(3) ); } -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::A", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..14, - focus_range: 7..8, - name: "A", - kind: Struct, - description: "struct A", - }, - }, - HoverGotoTypeData { - mod_path: "test::B", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 15..29, - focus_range: 22..23, - name: "B", - kind: Struct, - description: "struct B", - }, - }, - HoverGotoTypeData { - mod_path: "test::M::C", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 42..60, - focus_range: 53..54, - name: "C", - kind: Struct, - description: "pub struct C", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_return_impl_trait_has_goto_type_action() { - check_actions( - r#" -trait Foo {} -fn foo() -> impl Foo {} - -fn main() { let s$0t = foo(); } -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Foo", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..12, - focus_range: 6..9, - name: "Foo", - kind: Trait, - description: "trait Foo", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_generic_return_impl_trait_has_goto_type_action() { - check_actions( - r#" -trait Foo {} -struct S; -fn foo() -> impl Foo {} - -fn main() { let s$0t = foo(); } -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Foo", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..15, - focus_range: 6..9, - name: "Foo", - kind: Trait, - description: "trait Foo", - }, - }, - HoverGotoTypeData { - mod_path: "test::S", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 16..25, - focus_range: 23..24, - name: "S", - kind: Struct, - description: "struct S", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_return_impl_traits_has_goto_type_action() { - check_actions( - r#" -trait Foo {} -trait Bar {} -fn foo() -> impl Foo + Bar {} - -fn main() { let s$0t = foo(); } -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Foo", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..12, - focus_range: 6..9, - name: "Foo", - kind: Trait, - description: "trait Foo", - }, - }, - HoverGotoTypeData { - mod_path: "test::Bar", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 13..25, - focus_range: 19..22, - name: "Bar", - kind: Trait, - description: "trait Bar", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_generic_return_impl_traits_has_goto_type_action() { - check_actions( - r#" -trait Foo {} -trait Bar {} -struct S1 {} -struct S2 {} - -fn foo() -> impl Foo + Bar {} - -fn main() { let s$0t = foo(); } -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Foo", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..15, - focus_range: 6..9, - name: "Foo", - kind: Trait, - description: "trait Foo", - }, - }, - HoverGotoTypeData { - mod_path: "test::Bar", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 16..31, - focus_range: 22..25, - name: "Bar", - kind: Trait, - description: "trait Bar", - }, - }, - HoverGotoTypeData { - mod_path: "test::S1", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 32..44, - focus_range: 39..41, - name: "S1", - kind: Struct, - description: "struct S1", - }, - }, - HoverGotoTypeData { - mod_path: "test::S2", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 45..57, - focus_range: 52..54, - name: "S2", - kind: Struct, - description: "struct S2", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_arg_impl_trait_has_goto_type_action() { - check_actions( - r#" -trait Foo {} -fn foo(ar$0g: &impl Foo) {} -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Foo", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..12, - focus_range: 6..9, - name: "Foo", - kind: Trait, - description: "trait Foo", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_arg_impl_traits_has_goto_type_action() { - check_actions( - r#" -trait Foo {} -trait Bar {} -struct S{} - -fn foo(ar$0g: &impl Foo + Bar) {} -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Foo", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..12, - focus_range: 6..9, - name: "Foo", - kind: Trait, - description: "trait Foo", - }, - }, - HoverGotoTypeData { - mod_path: "test::Bar", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 13..28, - focus_range: 19..22, - name: "Bar", - kind: Trait, - description: "trait Bar", - }, - }, - HoverGotoTypeData { - mod_path: "test::S", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 29..39, - focus_range: 36..37, - name: "S", - kind: Struct, - description: "struct S", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - 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 }; -} -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "core::future::Future", - nav: NavigationTarget { - file_id: FileId( - 1, - ), - full_range: 254..436, - focus_range: 293..299, - name: "Future", - kind: Trait, - description: "pub trait Future", - }, - }, - HoverGotoTypeData { - mod_path: "test::S", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..9, - focus_range: 7..8, - name: "S", - kind: Struct, - description: "struct S", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_arg_generic_impl_trait_has_goto_type_action() { - check_actions( - r#" -trait Foo {} -struct S {} -fn foo(ar$0g: &impl Foo) {} -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Foo", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..15, - focus_range: 6..9, - name: "Foo", - kind: Trait, - description: "trait Foo", - }, - }, - HoverGotoTypeData { - mod_path: "test::S", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 16..27, - focus_range: 23..24, - name: "S", - kind: Struct, - description: "struct S", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_dyn_return_has_goto_type_action() { - check_actions( - r#" -trait Foo {} -struct S; -impl Foo for S {} - -struct B{} -fn foo() -> B {} - -fn main() { let s$0t = foo(); } -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::B", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 42..55, - focus_range: 49..50, - name: "B", - kind: Struct, - description: "struct B", - }, - }, - HoverGotoTypeData { - mod_path: "test::Foo", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..12, - focus_range: 6..9, - name: "Foo", - kind: Trait, - description: "trait Foo", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_dyn_arg_has_goto_type_action() { - check_actions( - r#" -trait Foo {} -fn foo(ar$0g: &dyn Foo) {} -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Foo", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..12, - focus_range: 6..9, - name: "Foo", - kind: Trait, - description: "trait Foo", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_generic_dyn_arg_has_goto_type_action() { - check_actions( - r#" -trait Foo {} -struct S {} -fn foo(ar$0g: &dyn Foo) {} -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Foo", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..15, - focus_range: 6..9, - name: "Foo", - kind: Trait, - description: "trait Foo", - }, - }, - HoverGotoTypeData { - mod_path: "test::S", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 16..27, - focus_range: 23..24, - name: "S", - kind: Struct, - description: "struct S", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_goto_type_action_links_order() { - check_actions( - r#" -trait ImplTrait {} -trait DynTrait {} -struct B {} -struct S {} - -fn foo(a$0rg: &impl ImplTrait>>>) {} -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::ImplTrait", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..21, - focus_range: 6..15, - name: "ImplTrait", - kind: Trait, - description: "trait ImplTrait", - }, - }, - HoverGotoTypeData { - mod_path: "test::B", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 43..57, - focus_range: 50..51, - name: "B", - kind: Struct, - description: "struct B", - }, - }, - HoverGotoTypeData { - mod_path: "test::DynTrait", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 22..42, - focus_range: 28..36, - name: "DynTrait", - kind: Trait, - description: "trait DynTrait", - }, - }, - HoverGotoTypeData { - mod_path: "test::S", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 58..69, - focus_range: 65..66, - name: "S", - kind: Struct, - description: "struct S", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_associated_type_has_goto_type_action() { - check_actions( - r#" -trait Foo { - type Item; - fn get(self) -> Self::Item {} -} - -struct Bar{} -struct S{} - -impl Foo for S { type Item = Bar; } - -fn test() -> impl Foo { S {} } - -fn main() { let s$0t = test().get(); } -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Foo", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..62, - focus_range: 6..9, - name: "Foo", - kind: Trait, - description: "trait Foo", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_const_param_has_goto_type_action() { - check_actions( - r#" -struct Bar; -struct Foo; - -impl Foo {} -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Bar", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..11, - focus_range: 7..10, - name: "Bar", - kind: Struct, - description: "struct Bar", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_type_param_has_goto_type_action() { - check_actions( - r#" -trait Foo {} - -fn foo(t: T$0){} -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Foo", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..12, - focus_range: 6..9, - name: "Foo", - kind: Trait, - description: "trait Foo", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn test_hover_self_has_go_to_type() { - check_actions( - r#" -struct Foo; -impl Foo { - fn foo(&self$0) {} -} -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Foo", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..11, - focus_range: 7..10, - name: "Foo", - kind: Struct, - description: "struct Foo", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn hover_displays_normalized_crate_names() { - check( - r#" -//- /lib.rs crate:name-with-dashes -pub mod wrapper { - pub struct Thing { x: u32 } - - impl Thing { - pub fn new() -> Thing { Thing { x: 0 } } - } -} - -//- /main.rs crate:main deps:name-with-dashes -fn main() { let foo_test = name_with_dashes::wrapper::Thing::new$0(); } -"#, - expect![[r#" - *new* - - ```rust - name_with_dashes::wrapper::Thing - ``` - - ```rust - pub fn new() -> Thing - ``` - "#]], - ) - } - - #[test] - fn hover_field_pat_shorthand_ref_match_ergonomics() { - check( - r#" -struct S { - f: i32, -} - -fn main() { - let s = S { f: 0 }; - let S { f$0 } = &s; -} -"#, - expect![[r#" - *f* - - ```rust - f: &i32 - ``` - --- - - ```rust - test::S - ``` - - ```rust - f: i32 - ``` - "#]], - ); - } - - #[test] - fn hover_self_param_shows_type() { - check( - r#" -struct Foo {} -impl Foo { - fn bar(&sel$0f) {} -} -"#, - expect![[r#" - *self* - - ```rust - self: &Foo - ``` - "#]], - ); - } - - #[test] - fn hover_self_param_shows_type_for_arbitrary_self_type() { - check( - r#" -struct Arc(T); -struct Foo {} -impl Foo { - fn bar(sel$0f: Arc) {} -} -"#, - expect![[r#" - *self* - - ```rust - self: Arc - ``` - "#]], - ); - } - - #[test] - fn hover_doc_outer_inner() { - check( - r#" -/// Be quick; -mod Foo$0 { - //! time is mana - - /// This comment belongs to the function - fn foo() {} -} -"#, - expect![[r#" - *Foo* - - ```rust - test - ``` - - ```rust - mod Foo - ``` - - --- - - Be quick; - time is mana - "#]], - ); - } - - #[test] - fn hover_doc_outer_inner_attribue() { - check( - r#" -#[doc = "Be quick;"] -mod Foo$0 { - #![doc = "time is mana"] - - #[doc = "This comment belongs to the function"] - fn foo() {} -} -"#, - expect![[r#" - *Foo* - - ```rust - test - ``` - - ```rust - mod Foo - ``` - - --- - - Be quick; - time is mana - "#]], - ); - } - - #[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() { - // no$0hover -} -"#, - ); - } - - #[test] - fn hover_label() { - check( - r#" -fn foo() { - 'label$0: loop {} -} -"#, - expect![[r#" - *'label* - - ```rust - 'label - ``` - "#]], - ); - } - - #[test] - fn hover_lifetime() { - check( - r#"fn foo<'lifetime>(_: &'lifetime$0 ()) {}"#, - expect![[r#" - *'lifetime* - - ```rust - 'lifetime - ``` - "#]], - ); - } - - #[test] - fn hover_type_param() { - check( - r#" -//- minicore: sized -struct Foo(T); -trait TraitA {} -trait TraitB {} -impl Foo where T: Sized {} -"#, - expect![[r#" - *T* - - ```rust - T: TraitA + TraitB - ``` - "#]], - ); - check( - r#" -//- minicore: sized -struct Foo(T); -impl Foo {} -"#, - expect![[r#" - *T* - - ```rust - T - ``` - "#]], - ); - // lifetimes bounds arent being tracked yet - check( - r#" -//- minicore: sized -struct Foo(T); -impl Foo {} -"#, - expect![[r#" - *T* - - ```rust - T - ``` - "#]], - ); - } - - #[test] - fn hover_type_param_sized_bounds() { - // implicit `: Sized` bound - check( - r#" -//- minicore: sized -trait Trait {} -struct Foo(T); -impl Foo {} -"#, - expect![[r#" - *T* - - ```rust - T: Trait - ``` - "#]], - ); - check( - r#" -//- minicore: sized -trait Trait {} -struct Foo(T); -impl Foo {} -"#, - expect![[r#" - *T* - - ```rust - T: Trait + ?Sized - ``` - "#]], - ); - } - - mod type_param_sized_bounds { - use super::*; - - #[test] - fn single_implicit() { - check( - r#" -//- minicore: sized -fn foo() {} -"#, - expect![[r#" - *T* - - ```rust - T - ``` - "#]], - ); - } - - #[test] - fn single_explicit() { - check( - r#" -//- minicore: sized -fn foo() {} -"#, - expect![[r#" - *T* - - ```rust - T - ``` - "#]], - ); - } - - #[test] - fn single_relaxed() { - check( - r#" -//- minicore: sized -fn foo() {} -"#, - expect![[r#" - *T* - - ```rust - T: ?Sized - ``` - "#]], - ); - } - - #[test] - fn multiple_implicit() { - check( - r#" -//- minicore: sized -trait Trait {} -fn foo() {} -"#, - expect![[r#" - *T* - - ```rust - T: Trait - ``` - "#]], - ); - } - - #[test] - fn multiple_explicit() { - check( - r#" -//- minicore: sized -trait Trait {} -fn foo() {} -"#, - expect![[r#" - *T* - - ```rust - T: Trait - ``` - "#]], - ); - } - - #[test] - fn multiple_relaxed() { - check( - r#" -//- minicore: sized -trait Trait {} -fn foo() {} -"#, - expect![[r#" - *T* - - ```rust - T: Trait + ?Sized - ``` - "#]], - ); - } - - #[test] - fn mixed() { - check( - r#" -//- minicore: sized -fn foo() {} -"#, - expect![[r#" - *T* - - ```rust - T - ``` - "#]], - ); - check( - r#" -//- minicore: sized -trait Trait {} -fn foo() {} -"#, - expect![[r#" - *T* - - ```rust - T: Trait - ``` - "#]], - ); - } - } - - #[test] - fn hover_const_param() { - check( - r#" -struct Foo; -impl Foo {} -"#, - expect![[r#" - *LEN* - - ```rust - const LEN: usize - ``` - "#]], - ); - } - - #[test] - fn hover_const_pat() { - check( - r#" -/// This is a doc -const FOO: usize = 3; -fn foo() { - match 5 { - FOO$0 => (), - _ => () - } -} -"#, - expect![[r#" - *FOO* - - ```rust - test - ``` - - ```rust - const FOO: usize - ``` - - --- - - This is a doc - "#]], - ); - } - - #[test] - fn hover_mod_def() { - check( - r#" -//- /main.rs -mod foo$0; -//- /foo.rs -//! For the horde! -"#, - expect![[r#" - *foo* - - ```rust - test - ``` - - ```rust - mod foo - ``` - - --- - - For the horde! - "#]], - ); - } - - #[test] - fn hover_self_in_use() { - check( - r#" -//! This should not appear -mod foo { - /// But this should appear - pub mod bar {} -} -use foo::bar::{self$0}; -"#, - expect![[r#" - *self* - - ```rust - test::foo - ``` - - ```rust - mod bar - ``` - - --- - - But this should appear - "#]], - ) - } - - #[test] - fn hover_keyword() { - check( - 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* - - ```rust - return - ``` - - --- - - Docs for return_keyword - "#]], - ); - } - - #[test] - fn hover_builtin() { - check( - r#" -//- /main.rs crate:main deps:std -cosnt _: &str$0 = ""; } - -//- /libstd.rs crate:std -/// Docs for prim_str -mod prim_str {} -"#, - expect![[r#" - *str* - - ```rust - str - ``` - - --- - - Docs for prim_str - "#]], - ); - } - - #[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() where T::Assoc$0: {} - -trait A { - type Assoc; -}"#, - expect![[r#" - *Assoc* - - ```rust - test - ``` - - ```rust - type Assoc - ``` - "#]], - ); - check( - r#" -fn foo() { - let _: ::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. - "#]], - ) - } - - #[test] - fn hover_attr_path_qualifier() { - cov_mark::check!(name_ref_classify_attr_path_qualifier); - check( - r#" -//- /foo.rs crate:foo - -//- /lib.rs crate:main.rs deps:foo -#[fo$0o::bar()] -struct Foo; -"#, - expect![[r#" - *foo* - - ```rust - extern crate foo - ``` - "#]], - ) - } - - #[test] - fn hover_rename() { - check( - r#" -use self as foo$0; -"#, - expect![[r#" - *foo* - - ```rust - extern crate test - ``` - "#]], - ); - check( - r#" -mod bar {} -use bar::{self as foo$0}; -"#, - expect![[r#" - *foo* - - ```rust - test - ``` - - ```rust - mod bar - ``` - "#]], - ); - check( - r#" -mod bar { - use super as foo$0; -} -"#, - expect![[r#" - *foo* - - ```rust - extern crate test - ``` - "#]], - ); - check( - r#" -use crate as foo$0; -"#, - expect![[r#" - *foo* - - ```rust - extern crate test - ``` - "#]], - ); - } - - #[test] - fn hover_attribute_in_macro() { - check( - r#" -macro_rules! identity { - ($struct:item) => { - $struct - }; -} -#[rustc_builtin_macro] -pub macro Copy {} -identity!{ - #[derive(Copy$0)] - struct Foo; -} -"#, - expect![[r#" - *Copy* - - ```rust - test - ``` - - ```rust - pub macro Copy - ``` - "#]], - ); - } - - #[test] - fn hover_derive_input() { - check( - r#" -#[rustc_builtin_macro] -pub macro Copy {} -#[derive(Copy$0)] -struct Foo; -"#, - expect![[r#" - *Copy* - - ```rust - test - ``` - - ```rust - pub macro Copy - ``` - "#]], - ); - check( - r#" -mod foo { - #[rustc_builtin_macro] - pub macro Copy {} -} -#[derive(foo::Copy$0)] -struct Foo; -"#, - expect![[r#" - *Copy* - - ```rust - test - ``` - - ```rust - pub macro Copy - ``` - "#]], - ); - } - - #[test] - fn hover_range_math() { - check_hover_range( - r#" -fn f() { let expr = $01 + 2 * 3$0 } -"#, - expect![[r#" - ```rust - i32 - ```"#]], - ); - - check_hover_range( - r#" -fn f() { let expr = 1 $0+ 2 * $03 } -"#, - expect![[r#" - ```rust - i32 - ```"#]], - ); - - check_hover_range( - r#" -fn f() { let expr = 1 + $02 * 3$0 } -"#, - expect![[r#" - ```rust - i32 - ```"#]], - ); - } - - #[test] - fn hover_range_arrays() { - check_hover_range( - r#" -fn f() { let expr = $0[1, 2, 3, 4]$0 } -"#, - expect![[r#" - ```rust - [i32; 4] - ```"#]], - ); - - check_hover_range( - r#" -fn f() { let expr = [1, 2, $03, 4]$0 } -"#, - expect![[r#" - ```rust - [i32; 4] - ```"#]], - ); - - check_hover_range( - r#" -fn f() { let expr = [1, 2, $03$0, 4] } -"#, - expect![[r#" - ```rust - i32 - ```"#]], - ); - } - - #[test] - fn hover_range_functions() { - check_hover_range( - r#" -fn f(a: &[T]) { } -fn b() { $0f$0(&[1, 2, 3, 4, 5]); } -"#, - expect![[r#" - ```rust - fn f(&[i32]) - ```"#]], - ); - - check_hover_range( - r#" -fn f(a: &[T]) { } -fn b() { f($0&[1, 2, 3, 4, 5]$0); } -"#, - expect![[r#" - ```rust - &[i32; 5] - ```"#]], - ); - } - - #[test] - fn hover_range_shows_nothing_when_invalid() { - check_hover_range_no_results( - r#" -fn f(a: &[T]) { } -fn b()$0 { f(&[1, 2, 3, 4, 5]); }$0 -"#, - ); - - check_hover_range_no_results( - r#" -fn f$0(a: &[T]) { } -fn b() { f(&[1, 2, 3,$0 4, 5]); } -"#, - ); - - check_hover_range_no_results( - r#" -fn $0f() { let expr = [1, 2, 3, 4]$0 } -"#, - ); - } - - #[test] - fn hover_range_shows_unit_for_statements() { - check_hover_range( - r#" -fn f(a: &[T]) { } -fn b() { $0f(&[1, 2, 3, 4, 5]); }$0 -"#, - expect![[r#" - ```rust - () - ```"#]], - ); - - check_hover_range( - r#" -fn f() { let expr$0 = $0[1, 2, 3, 4] } -"#, - expect![[r#" - ```rust - () - ```"#]], - ); - } - - #[test] - fn hover_range_for_pat() { - check_hover_range( - r#" -fn foo() { - let $0x$0 = 0; -} -"#, - expect![[r#" - ```rust - i32 - ```"#]], - ); - - check_hover_range( - r#" -fn foo() { - let $0x$0 = ""; -} -"#, - expect![[r#" - ```rust - &str - ```"#]], - ); - } - - #[test] - fn hover_range_shows_coercions_if_applicable_expr() { - check_hover_range( - r#" -fn foo() { - let x: &u32 = $0&&&&&0$0; -} -"#, - expect![[r#" - ```text - Type: &&&&&u32 - Coerced to: &u32 - ``` - "#]], - ); - check_hover_range( - r#" -fn foo() { - let x: *const u32 = $0&0$0; -} -"#, - expect![[r#" - ```text - Type: &u32 - Coerced to: *const u32 - ``` - "#]], - ); - } - - #[test] - fn hover_range_shows_type_actions() { - check_actions( - r#" -struct Foo; -fn foo() { - let x: &Foo = $0&&&&&Foo$0; -} -"#, - expect![[r#" - [ - GoToType( - [ - HoverGotoTypeData { - mod_path: "test::Foo", - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..11, - focus_range: 7..10, - name: "Foo", - kind: Struct, - description: "struct Foo", - }, - }, - ], - ), - ] - "#]], - ); - } - - #[test] - fn hover_try_expr_res() { - check_hover_range( - r#" -//- minicore:result -struct FooError; - -fn foo() -> Result<(), FooError> { - Ok($0Result::<(), FooError>::Ok(())?$0) -} -"#, - expect![[r#" - ```rust - () - ```"#]], - ); - check_hover_range( - r#" -//- minicore:result -struct FooError; -struct BarError; - -fn foo() -> Result<(), FooError> { - Ok($0Result::<(), BarError>::Ok(())?$0) -} -"#, - expect![[r#" - ```text - Try Error Type: BarError - Propagated as: FooError - ``` - "#]], - ); - } - - #[test] - fn hover_try_expr() { - check_hover_range( - r#" -struct NotResult(T, U); -struct Short; -struct Looooong; - -fn foo() -> NotResult<(), Looooong> { - $0NotResult((), Short)?$0; -} -"#, - expect![[r#" - ```text - Try Target Type: NotResult<(), Short> - Propagated as: NotResult<(), Looooong> - ``` - "#]], - ); - check_hover_range( - r#" -struct NotResult(T, U); -struct Short; -struct Looooong; - -fn foo() -> NotResult<(), Short> { - $0NotResult((), Looooong)?$0; -} -"#, - expect![[r#" - ```text - Try Target Type: NotResult<(), Looooong> - Propagated as: NotResult<(), Short> - ``` - "#]], - ); - } - - #[test] - fn hover_try_expr_option() { - cov_mark::check!(hover_try_expr_opt_opt); - check_hover_range( - r#" -//- minicore: option, try - -fn foo() -> Option<()> { - $0Some(0)?$0; - None -} -"#, - expect![[r#" - ```rust - as Try>::Output - ```"#]], - ); - } - - #[test] - fn hover_deref_expr() { - check_hover_range( - r#" -//- minicore: deref -use core::ops::Deref; - -struct DerefExample { - value: T -} - -impl Deref for DerefExample { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.value - } -} - -fn foo() { - let x = DerefExample { value: 0 }; - let y: i32 = $0*x$0; -} -"#, - expect![[r#" - ```text - Dereferenced from: DerefExample - To type: i32 - ``` - "#]], - ); - } - - #[test] - fn hover_deref_expr_with_coercion() { - check_hover_range( - r#" -//- minicore: deref -use core::ops::Deref; - -struct DerefExample { - value: T -} - -impl Deref for DerefExample { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.value - } -} - -fn foo() { - let x = DerefExample { value: &&&&&0 }; - let y: &i32 = $0*x$0; -} -"#, - expect![[r#" - ```text - Dereferenced from: DerefExample<&&&&&i32> - To type: &&&&&i32 - Coerced to: &i32 - ``` - "#]], - ); - } - - #[test] - fn hover_intra_in_macro() { - check( - r#" -macro_rules! foo_macro { - ($(#[$attr:meta])* $name:ident) => { - $(#[$attr])* - pub struct $name; - } -} - -foo_macro!( - /// Doc comment for [`Foo$0`] - Foo -); -"#, - expect![[r#" - *[`Foo`]* - - ```rust - test - ``` - - ```rust - pub struct Foo - ``` - - --- - - Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html) - "#]], - ); - } - - #[test] - fn hover_intra_in_attr() { - check( - r#" -#[doc = "Doc comment for [`Foo$0`]"] -pub struct Foo; -"#, - expect![[r#" - *[`Foo`]* - - ```rust - test - ``` - - ```rust - pub struct Foo - ``` - - --- - - Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html) - "#]], - ); - } -} diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs new file mode 100644 index 0000000000..5b516de4c6 --- /dev/null +++ b/crates/ide/src/hover/render.rs @@ -0,0 +1,450 @@ +//! Logic for rendering the different hover messages +use either::Either; +use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo}; +use ide_db::{ + base_db::SourceDatabase, + defs::Definition, + helpers::{ + generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES}, + FamousDefs, + }, + RootDatabase, +}; +use itertools::Itertools; +use stdx::format_to; +use syntax::{ + algo, ast, + display::{fn_as_proc_macro_label, macro_label}, + match_ast, AstNode, Direction, + SyntaxKind::{CONDITION, LET_STMT}, + SyntaxToken, T, +}; + +use crate::{ + doc_links::{remove_links, rewrite_links}, + hover::walk_and_push_ty, + markdown_remove::remove_markdown, + HoverAction, HoverConfig, HoverResult, Markup, +}; + +pub(super) fn type_info( + sema: &Semantics, + config: &HoverConfig, + expr_or_pat: &Either, +) -> Option { + let TypeInfo { original, adjusted } = match expr_or_pat { + Either::Left(expr) => sema.type_of_expr(expr)?, + Either::Right(pat) => sema.type_of_pat(pat)?, + }; + + let mut res = HoverResult::default(); + let mut targets: Vec = Vec::new(); + let mut push_new_def = |item: hir::ModuleDef| { + if !targets.contains(&item) { + targets.push(item); + } + }; + walk_and_push_ty(sema.db, &original, &mut push_new_def); + + res.markup = if let Some(adjusted_ty) = adjusted { + walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def); + let original = original.display(sema.db).to_string(); + let adjusted = adjusted_ty.display(sema.db).to_string(); + let static_text_diff_len = "Coerced to: ".len() - "Type: ".len(); + format!( + "{bt_start}Type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}", + original, + adjusted, + apad = static_text_diff_len + adjusted.len().max(original.len()), + opad = original.len(), + bt_start = if config.markdown() { "```text\n" } else { "" }, + bt_end = if config.markdown() { "```\n" } else { "" } + ) + .into() + } else { + if config.markdown() { + Markup::fenced_block(&original.display(sema.db)) + } else { + original.display(sema.db).to_string().into() + } + }; + res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets)); + Some(res) +} + +pub(super) fn try_expr( + sema: &Semantics, + config: &HoverConfig, + try_expr: &ast::TryExpr, +) -> Option { + let inner_ty = sema.type_of_expr(&try_expr.expr()?)?.original; + let mut ancestors = try_expr.syntax().ancestors(); + let mut body_ty = loop { + let next = ancestors.next()?; + break match_ast! { + match next { + ast::Fn(fn_) => sema.to_def(&fn_)?.ret_type(sema.db), + ast::Item(__) => return None, + ast::ClosureExpr(closure) => sema.type_of_expr(&closure.body()?)?.original, + ast::EffectExpr(effect) => if matches!(effect.effect(), ast::Effect::Async(_) | ast::Effect::Try(_)| ast::Effect::Const(_)) { + sema.type_of_expr(&effect.block_expr()?.into())?.original + } else { + continue; + }, + _ => continue, + } + }; + }; + + if inner_ty == body_ty { + return None; + } + + let mut inner_ty = inner_ty; + let mut s = "Try Target".to_owned(); + + let adts = inner_ty.as_adt().zip(body_ty.as_adt()); + if let Some((hir::Adt::Enum(inner), hir::Adt::Enum(body))) = adts { + let famous_defs = FamousDefs(sema, sema.scope(&try_expr.syntax()).krate()); + // special case for two options, there is no value in showing them + if let Some(option_enum) = famous_defs.core_option_Option() { + if inner == option_enum && body == option_enum { + cov_mark::hit!(hover_try_expr_opt_opt); + return None; + } + } + + // special case two results to show the error variants only + if let Some(result_enum) = famous_defs.core_result_Result() { + if inner == result_enum && body == result_enum { + let error_type_args = + inner_ty.type_arguments().nth(1).zip(body_ty.type_arguments().nth(1)); + if let Some((inner, body)) = error_type_args { + inner_ty = inner; + body_ty = body; + s = "Try Error".to_owned(); + } + } + } + } + + let mut res = HoverResult::default(); + + let mut targets: Vec = Vec::new(); + let mut push_new_def = |item: hir::ModuleDef| { + if !targets.contains(&item) { + targets.push(item); + } + }; + walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def); + walk_and_push_ty(sema.db, &body_ty, &mut push_new_def); + res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets)); + + let inner_ty = inner_ty.display(sema.db).to_string(); + let body_ty = body_ty.display(sema.db).to_string(); + let ty_len_max = inner_ty.len().max(body_ty.len()); + + let l = "Propagated as: ".len() - " Type: ".len(); + let static_text_len_diff = l as isize - s.len() as isize; + let tpad = static_text_len_diff.max(0) as usize; + let ppad = static_text_len_diff.min(0).abs() as usize; + + res.markup = format!( + "{bt_start}{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n{bt_end}", + s, + inner_ty, + body_ty, + pad0 = ty_len_max + tpad, + pad1 = ty_len_max + ppad, + bt_start = if config.markdown() { "```text\n" } else { "" }, + bt_end = if config.markdown() { "```\n" } else { "" } + ) + .into(); + Some(res) +} + +pub(super) fn deref_expr( + sema: &Semantics, + config: &HoverConfig, + deref_expr: &ast::PrefixExpr, +) -> Option { + let inner_ty = sema.type_of_expr(&deref_expr.expr()?)?.original; + let TypeInfo { original, adjusted } = + sema.type_of_expr(&ast::Expr::from(deref_expr.clone()))?; + + let mut res = HoverResult::default(); + let mut targets: Vec = Vec::new(); + let mut push_new_def = |item: hir::ModuleDef| { + if !targets.contains(&item) { + targets.push(item); + } + }; + walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def); + walk_and_push_ty(sema.db, &original, &mut push_new_def); + + res.markup = if let Some(adjusted_ty) = adjusted { + walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def); + let original = original.display(sema.db).to_string(); + let adjusted = adjusted_ty.display(sema.db).to_string(); + let inner = inner_ty.display(sema.db).to_string(); + let type_len = "To type: ".len(); + let coerced_len = "Coerced to: ".len(); + let deref_len = "Dereferenced from: ".len(); + let max_len = (original.len() + type_len) + .max(adjusted.len() + coerced_len) + .max(inner.len() + deref_len); + format!( + "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}", + inner, + original, + adjusted, + ipad = max_len - deref_len, + apad = max_len - type_len, + opad = max_len - coerced_len, + bt_start = if config.markdown() { "```text\n" } else { "" }, + bt_end = if config.markdown() { "```\n" } else { "" } + ) + .into() + } else { + let original = original.display(sema.db).to_string(); + let inner = inner_ty.display(sema.db).to_string(); + let type_len = "To type: ".len(); + let deref_len = "Dereferenced from: ".len(); + let max_len = (original.len() + type_len).max(inner.len() + deref_len); + format!( + "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\n{bt_end}", + inner, + original, + ipad = max_len - deref_len, + apad = max_len - type_len, + bt_start = if config.markdown() { "```text\n" } else { "" }, + bt_end = if config.markdown() { "```\n" } else { "" } + ) + .into() + }; + res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets)); + + Some(res) +} + +pub(super) fn keyword( + sema: &Semantics, + config: &HoverConfig, + token: &SyntaxToken, +) -> Option { + if !token.kind().is_keyword() || !config.documentation.is_some() { + return None; + } + 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)?; + let docs = doc_owner.attrs(sema.db).docs()?; + let markup = process_markup( + sema.db, + Definition::ModuleDef(doc_owner.into()), + &markup(Some(docs.into()), token.text().into(), None)?, + config, + ); + Some(HoverResult { markup, actions: Default::default() }) +} + +pub(super) fn try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option { + 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(HoverResult { + markup: Markup::from(format!("```\n{}\n```\n___\n\n{}", lint.label, lint.description)), + ..Default::default() + }) +} + +pub(super) fn process_markup( + db: &RootDatabase, + def: Definition, + markup: &Markup, + config: &HoverConfig, +) -> Markup { + let markup = markup.as_str(); + let markup = if !config.markdown() { + remove_markdown(markup) + } else if config.links_in_hover { + rewrite_links(db, markup, def) + } else { + remove_links(markup) + }; + Markup::from(markup) +} + +fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option { + match def { + Definition::Field(f) => Some(f.parent_def(db).name(db)), + Definition::Local(l) => l.parent(db).name(db), + Definition::ModuleDef(md) => match md { + hir::ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) { + hir::AssocItemContainer::Trait(t) => Some(t.name(db)), + hir::AssocItemContainer::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)), + }, + hir::ModuleDef::Variant(e) => Some(e.parent_enum(db).name(db)), + _ => None, + }, + _ => None, + } + .map(|name| name.to_string()) +} + +pub(super) fn path(db: &RootDatabase, module: hir::Module, item_name: Option) -> String { + let crate_name = + db.crate_graph()[module.krate().into()].display_name.as_ref().map(|it| it.to_string()); + let module_path = module + .path_to_root(db) + .into_iter() + .rev() + .flat_map(|it| it.name(db).map(|name| name.to_string())); + crate_name.into_iter().chain(module_path).chain(item_name).join("::") +} + +pub(super) fn definition( + db: &RootDatabase, + def: Definition, + famous_defs: Option<&FamousDefs>, + config: &HoverConfig, +) -> Option { + let mod_path = definition_mod_path(db, &def); + let (label, docs) = match def { + Definition::Macro(it) => ( + match &it.source(db)?.value { + Either::Left(mac) => macro_label(mac), + Either::Right(mac_fn) => fn_as_proc_macro_label(mac_fn), + }, + it.attrs(db).docs(), + ), + Definition::Field(def) => label_and_docs(db, def), + Definition::ModuleDef(it) => match it { + hir::ModuleDef::Module(it) => label_and_docs(db, it), + hir::ModuleDef::Function(it) => label_and_docs(db, it), + hir::ModuleDef::Adt(it) => label_and_docs(db, it), + hir::ModuleDef::Variant(it) => label_and_docs(db, it), + hir::ModuleDef::Const(it) => label_and_docs(db, it), + hir::ModuleDef::Static(it) => label_and_docs(db, it), + hir::ModuleDef::Trait(it) => label_and_docs(db, it), + hir::ModuleDef::TypeAlias(it) => label_and_docs(db, it), + hir::ModuleDef::BuiltinType(it) => { + return famous_defs + .and_then(|fd| builtin(fd, it)) + .or_else(|| Some(Markup::fenced_block(&it.name()))) + } + }, + Definition::Local(it) => return local(db, it), + Definition::SelfType(impl_def) => { + impl_def.self_ty(db).as_adt().map(|adt| label_and_docs(db, adt))? + } + Definition::GenericParam(it) => label_and_docs(db, it), + Definition::Label(it) => return Some(Markup::fenced_block(&it.name(db))), + }; + + markup(docs.filter(|_| config.documentation.is_some()).map(Into::into), label, mod_path) +} + +fn label_and_docs(db: &RootDatabase, def: D) -> (String, Option) +where + D: HasAttrs + HirDisplay, +{ + let label = def.display(db).to_string(); + let docs = def.attrs(db).docs(); + (label, docs) +} + +fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option { + if let Definition::GenericParam(_) = def { + return None; + } + def.module(db).map(|module| path(db, module, definition_owner_name(db, def))) +} + +fn markup(docs: Option, desc: String, mod_path: Option) -> Option { + let mut buf = String::new(); + + if let Some(mod_path) = mod_path { + if !mod_path.is_empty() { + format_to!(buf, "```rust\n{}\n```\n\n", mod_path); + } + } + format_to!(buf, "```rust\n{}\n```", desc); + + if let Some(doc) = docs { + format_to!(buf, "\n___\n\n{}", doc); + } + Some(buf.into()) +} + +fn builtin(famous_defs: &FamousDefs, builtin: hir::BuiltinType) -> Option { + // std exposes prim_{} modules with docstrings on the root to document the builtins + let primitive_mod = format!("prim_{}", builtin.name()); + let doc_owner = find_std_module(famous_defs, &primitive_mod)?; + let docs = doc_owner.attrs(famous_defs.0.db).docs()?; + markup(Some(docs.into()), builtin.name().to_string(), None) +} + +fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option { + let db = famous_defs.0.db; + let std_crate = famous_defs.std()?; + let std_root_module = std_crate.root_module(db); + std_root_module + .children(db) + .find(|module| module.name(db).map_or(false, |module| module.to_string() == name)) +} + +fn local(db: &RootDatabase, it: hir::Local) -> Option { + 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), + }; + markup(None, desc, None) +} diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs new file mode 100644 index 0000000000..463cd58e48 --- /dev/null +++ b/crates/ide/src/hover/tests.rs @@ -0,0 +1,4168 @@ +use expect_test::{expect, Expect}; +use ide_db::base_db::{FileLoader, FileRange}; +use syntax::TextRange; + +use crate::{fixture, hover::HoverDocFormat, HoverConfig}; + +fn check_hover_no_result(ra_fixture: &str) { + let (analysis, position) = fixture::position(ra_fixture); + let hover = analysis + .hover( + &HoverConfig { links_in_hover: true, documentation: Some(HoverDocFormat::Markdown) }, + FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, + ) + .unwrap(); + assert!(hover.is_none(), "hover not expected but found: {:?}", hover.unwrap()); +} + +fn check(ra_fixture: &str, expect: Expect) { + let (analysis, position) = fixture::position(ra_fixture); + let hover = analysis + .hover( + &HoverConfig { links_in_hover: true, documentation: Some(HoverDocFormat::Markdown) }, + 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[hover.range]; + + let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup); + expect.assert_eq(&actual) +} + +fn check_hover_no_links(ra_fixture: &str, expect: Expect) { + let (analysis, position) = fixture::position(ra_fixture); + let hover = analysis + .hover( + &HoverConfig { links_in_hover: false, documentation: Some(HoverDocFormat::Markdown) }, + 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[hover.range]; + + let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup); + expect.assert_eq(&actual) +} + +fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) { + let (analysis, position) = fixture::position(ra_fixture); + let hover = analysis + .hover( + &HoverConfig { links_in_hover: true, documentation: Some(HoverDocFormat::PlainText) }, + 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[hover.range]; + + let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup); + expect.assert_eq(&actual) +} + +fn check_actions(ra_fixture: &str, expect: Expect) { + let (analysis, file_id, position) = fixture::range_or_position(ra_fixture); + let hover = analysis + .hover( + &HoverConfig { links_in_hover: true, documentation: Some(HoverDocFormat::Markdown) }, + FileRange { file_id, range: position.range_or_empty() }, + ) + .unwrap() + .unwrap(); + expect.assert_debug_eq(&hover.info.actions) +} + +fn check_hover_range(ra_fixture: &str, expect: Expect) { + let (analysis, range) = fixture::range(ra_fixture); + let hover = analysis + .hover( + &HoverConfig { links_in_hover: false, documentation: Some(HoverDocFormat::Markdown) }, + range, + ) + .unwrap() + .unwrap(); + expect.assert_eq(hover.info.markup.as_str()) +} + +fn check_hover_range_no_results(ra_fixture: &str) { + let (analysis, range) = fixture::range(ra_fixture); + let hover = analysis + .hover( + &HoverConfig { links_in_hover: false, documentation: Some(HoverDocFormat::Markdown) }, + 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 + test::module + ``` + + ```rust + fn abc() + ``` + + --- + + Inner + --- + + ```rust + test + ``` + + ```rust + fn abc() + ``` + + --- + + Outer + "#]], + ); +} + +#[test] +fn hover_shows_type_of_an_expression() { + check( + r#" +pub fn foo() -> u32 { 1 } + +fn main() { + let foo_test = foo()$0; +} +"#, + expect![[r#" + *foo()* + ```rust + u32 + ``` + "#]], + ); +} + +#[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()* + u32 + "#]], + ); +} + +#[test] +fn hover_shows_long_type_of_an_expression() { + check( + r#" +struct Scan { a: A, b: B, c: C } +struct Iter { inner: I } +enum Option { Some(T), None } + +struct OtherStruct { i: T } + +fn scan(a: A, b: B, c: C) -> Iter, B, C>> { + Iter { inner: Scan { a, b, c } } +} + +fn main() { + let num: i32 = 55; + let closure = |memo: &mut u32, value: &u32, _another: &mut u32| -> Option { + Option::Some(*memo + value) + }; + let number = 5u32; + let mut iter$0 = scan(OtherStruct { i: num }, closure, number); +} +"#, + expect![[r#" + *iter* + + ```rust + let mut iter: Iter>, |&mut u32, &u32, &mut u32| -> Option, u32>> + ``` + "#]], + ); +} + +#[test] +fn hover_shows_fn_signature() { + // Single file with result + check( + r#" +pub fn foo() -> u32 { 1 } + +fn main() { let foo_test = fo$0o(); } +"#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + pub fn foo() -> u32 + ``` + "#]], + ); + + // Multiple candidates but results are ambiguous. + check( + r#" +//- /a.rs +pub fn foo() -> u32 { 1 } + +//- /b.rs +pub fn foo() -> &str { "" } + +//- /c.rs +pub fn foo(a: u32, b: u32) {} + +//- /main.rs +mod a; +mod b; +mod c; + +fn main() { let foo_test = fo$0o(); } + "#, + expect![[r#" + *foo* + ```rust + {unknown} + ``` + "#]], + ); + + // Use literal `crate` in path + check( + r#" +pub struct X; + +fn foo() -> crate::X { X } + +fn main() { f$0oo(); } + "#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + fn foo() -> crate::X + ``` + "#]], + ); + + // Check `super` in path + check( + r#" +pub struct X; + +mod m { pub fn foo() -> super::X { super::X } } + +fn main() { m::f$0oo(); } + "#, + expect![[r#" + *foo* + + ```rust + test::m + ``` + + ```rust + pub fn foo() -> super::X + ``` + "#]], + ); +} + +#[test] +fn hover_shows_fn_signature_with_type_params() { + check( + r#" +pub fn foo<'a, T: AsRef>(b: &'a T) -> &'a str { } + +fn main() { let foo_test = fo$0o(); } + "#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + pub fn foo<'a, T>(b: &'a T) -> &'a str + where + T: AsRef, + ``` + "#]], + ); +} + +#[test] +fn hover_shows_fn_signature_on_fn_name() { + check( + r#" +pub fn foo$0(a: u32, b: u32) -> u32 {} + +fn main() { } +"#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + pub fn foo(a: u32, b: u32) -> u32 + ``` + "#]], + ); +} + +#[test] +fn hover_shows_fn_doc() { + check( + r#" +/// # Example +/// ``` +/// # use std::path::Path; +/// # +/// foo(Path::new("hello, world!")) +/// ``` +pub fn foo$0(_: &Path) {} + +fn main() { } +"#, + expect![[r##" + *foo* + + ```rust + test + ``` + + ```rust + pub fn foo(_: &Path) + ``` + + --- + + # Example + + ``` + # use std::path::Path; + # + foo(Path::new("hello, world!")) + ``` + "##]], + ); +} + +#[test] +fn hover_shows_fn_doc_attr_raw_string() { + check( + r##" +#[doc = r#"Raw string doc attr"#] +pub fn foo$0(_: &Path) {} + +fn main() { } +"##, + expect![[r##" + *foo* + + ```rust + test + ``` + + ```rust + pub fn foo(_: &Path) + ``` + + --- + + Raw string doc attr + "##]], + ); +} + +#[test] +fn hover_shows_struct_field_info() { + // Hovering over the field when instantiating + check( + r#" +struct Foo { field_a: u32 } + +fn main() { + let foo = Foo { field_a$0: 0, }; +} +"#, + expect![[r#" + *field_a* + + ```rust + test::Foo + ``` + + ```rust + field_a: u32 + ``` + "#]], + ); + + // Hovering over the field in the definition + check( + r#" +struct Foo { field_a$0: u32 } + +fn main() { + let foo = Foo { field_a: 0 }; +} +"#, + expect![[r#" + *field_a* + + ```rust + test::Foo + ``` + + ```rust + field_a: u32 + ``` + "#]], + ); +} + +#[test] +fn hover_const_static() { + check( + r#"const foo$0: u32 = 123;"#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + const foo: u32 + ``` + "#]], + ); + check( + r#"static foo$0: u32 = 456;"#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + static foo: u32 + ``` + "#]], + ); +} + +#[test] +fn hover_default_generic_types() { + check( + r#" +struct Test { k: K, t: T } + +fn main() { + let zz$0 = Test { t: 23u8, k: 33 }; +}"#, + expect![[r#" + *zz* + + ```rust + let zz: Test + ``` + "#]], + ); +} + +#[test] +fn hover_some() { + check( + r#" +enum Option { Some(T) } +use Option::Some; + +fn main() { So$0me(12); } +"#, + expect![[r#" + *Some* + + ```rust + test::Option + ``` + + ```rust + Some(T) + ``` + "#]], + ); + + check( + r#" +enum Option { Some(T) } +use Option::Some; + +fn main() { let b$0ar = Some(12); } +"#, + expect![[r#" + *bar* + + ```rust + let bar: Option + ``` + "#]], + ); +} + +#[test] +fn hover_enum_variant() { + check( + r#" +enum Option { + /// The None variant + Non$0e +} +"#, + expect![[r#" + *None* + + ```rust + test::Option + ``` + + ```rust + None + ``` + + --- + + The None variant + "#]], + ); + + check( + r#" +enum Option { + /// The Some variant + Some(T) +} +fn main() { + let s = Option::Som$0e(12); +} +"#, + expect![[r#" + *Some* + + ```rust + test::Option + ``` + + ```rust + Some(T) + ``` + + --- + + The Some variant + "#]], + ); +} + +#[test] +fn hover_for_local_variable() { + check( + r#"fn func(foo: i32) { fo$0o; }"#, + expect![[r#" + *foo* + + ```rust + foo: i32 + ``` + "#]], + ) +} + +#[test] +fn hover_for_local_variable_pat() { + check( + r#"fn func(fo$0o: i32) {}"#, + expect![[r#" + *foo* + + ```rust + foo: i32 + ``` + "#]], + ) +} + +#[test] +fn hover_local_var_edge() { + check( + r#"fn func(foo: i32) { if true { $0foo; }; }"#, + expect![[r#" + *foo* + + ```rust + foo: i32 + ``` + "#]], + ) +} + +#[test] +fn hover_for_param_edge() { + check( + r#"fn func($0foo: i32) {}"#, + expect![[r#" + *foo* + + ```rust + foo: i32 + ``` + "#]], + ) +} + +#[test] +fn hover_for_param_with_multiple_traits() { + check( + r#" + //- minicore: sized + trait Deref { + type Target: ?Sized; + } + trait DerefMut { + type Target: ?Sized; + } + fn f(_x$0: impl Deref + DerefMut) {}"#, + expect![[r#" + *_x* + + ```rust + _x: impl Deref + DerefMut + ``` + "#]], + ) +} + +#[test] +fn test_hover_infer_associated_method_result() { + check( + r#" +struct Thing { x: u32 } + +impl Thing { + fn new() -> Thing { Thing { x: 0 } } +} + +fn main() { let foo_$0test = Thing::new(); } +"#, + expect![[r#" + *foo_test* + + ```rust + let foo_test: Thing + ``` + "#]], + ) +} + +#[test] +fn test_hover_infer_associated_method_exact() { + check( + r#" +mod wrapper { + struct Thing { x: u32 } + + impl Thing { + fn new() -> Thing { Thing { x: 0 } } + } +} + +fn main() { let foo_test = wrapper::Thing::new$0(); } +"#, + expect![[r#" + *new* + + ```rust + test::wrapper::Thing + ``` + + ```rust + fn new() -> Thing + ``` + "#]], + ) +} + +#[test] +fn test_hover_infer_associated_const_in_pattern() { + check( + r#" +struct X; +impl X { + const C: u32 = 1; +} + +fn main() { + match 1 { + X::C$0 => {}, + 2 => {}, + _ => {} + }; +} +"#, + expect![[r#" + *C* + + ```rust + test + ``` + + ```rust + const C: u32 + ``` + "#]], + ) +} + +#[test] +fn test_hover_self() { + check( + r#" +struct Thing { x: u32 } +impl Thing { + fn new() -> Self { Self$0 { x: 0 } } +} +"#, + expect![[r#" + *Self* + + ```rust + test + ``` + + ```rust + struct Thing + ``` + "#]], + ); + check( + r#" +struct Thing { x: u32 } +impl Thing { + fn new() -> Self$0 { Self { x: 0 } } +} +"#, + expect![[r#" + *Self* + + ```rust + test + ``` + + ```rust + struct Thing + ``` + "#]], + ); + check( + r#" +enum Thing { A } +impl Thing { + pub fn new() -> Self$0 { Thing::A } +} +"#, + expect![[r#" + *Self* + + ```rust + test + ``` + + ```rust + enum Thing + ``` + "#]], + ); + check( + r#" + enum Thing { A } + impl Thing { + pub fn thing(a: Self$0) {} + } + "#, + expect![[r#" + *Self* + + ```rust + test + ``` + + ```rust + enum Thing + ``` + "#]], + ); +} + +#[test] +fn test_hover_shadowing_pat() { + check( + r#" +fn x() {} + +fn y() { + let x = 0i32; + x$0; +} +"#, + expect![[r#" + *x* + + ```rust + let x: i32 + ``` + "#]], + ) +} + +#[test] +fn test_hover_macro_invocation() { + check( + r#" +macro_rules! foo { () => {} } + +fn f() { fo$0o!(); } +"#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + macro_rules! foo + ``` + "#]], + ) +} + +#[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( + r#"struct TS(String, i32$0);"#, + expect![[r#" + *i32* + + ```rust + i32 + ``` + "#]], + ) +} + +#[test] +fn test_hover_through_macro() { + check( + r#" +macro_rules! id { ($($tt:tt)*) => { $($tt)* } } +fn foo() {} +id! { + fn bar() { fo$0o(); } +} +"#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + fn foo() + ``` + "#]], + ); +} + +#[test] +fn test_hover_through_attr() { + check( + r#" +//- proc_macros: identity +#[proc_macros::identity] +fn foo$0() {} +"#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + fn foo() + ``` + "#]], + ); +} + +#[test] +fn test_hover_through_expr_in_macro() { + check( + r#" +macro_rules! id { ($($tt:tt)*) => { $($tt)* } } +fn foo(bar:u32) { let a = id!(ba$0r); } +"#, + expect![[r#" + *bar* + + ```rust + bar: u32 + ``` + "#]], + ); +} + +#[test] +fn test_hover_through_expr_in_macro_recursive() { + check( + r#" +macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } } +macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } } +fn foo(bar:u32) { let a = id!(ba$0r); } +"#, + expect![[r#" + *bar* + + ```rust + bar: u32 + ``` + "#]], + ); +} + +#[test] +fn test_hover_through_func_in_macro_recursive() { + check( + r#" +macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } } +macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } } +fn bar() -> u32 { 0 } +fn foo() { let a = id!([0u32, bar($0)] ); } +"#, + expect![[r#" + *bar()* + ```rust + u32 + ``` + "#]], + ); +} + +#[test] +fn test_hover_through_literal_string_in_macro() { + check( + r#" +macro_rules! arr { ($($tt:tt)*) => { [$($tt)*)] } } +fn foo() { + let mastered_for_itunes = ""; + let _ = arr!("Tr$0acks", &mastered_for_itunes); +} +"#, + expect![[r#" + *"Tracks"* + ```rust + &str + ``` + "#]], + ); +} + +#[test] +fn test_hover_through_assert_macro() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! assert {} + +fn bar() -> bool { true } +fn foo() { + assert!(ba$0r()); +} +"#, + expect![[r#" + *bar* + + ```rust + test + ``` + + ```rust + fn bar() -> bool + ``` + "#]], + ); +} + +#[test] +fn test_hover_through_literal_string_in_builtin_macro() { + check_hover_no_result( + r#" + #[rustc_builtin_macro] + macro_rules! format {} + + fn foo() { + format!("hel$0lo {}", 0); + } +"#, + ); +} + +#[test] +fn test_hover_non_ascii_space_doc() { + check( + " +/// <- `\u{3000}` here +fn foo() { } + +fn bar() { fo$0o(); } +", + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + fn foo() + ``` + + --- + + \<- ` ` here + "#]], + ); +} + +#[test] +fn test_hover_function_show_qualifiers() { + check( + r#"async fn foo$0() {}"#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + async fn foo() + ``` + "#]], + ); + check( + r#"pub const unsafe fn foo$0() {}"#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + pub const unsafe fn foo() + ``` + "#]], + ); + // Top level `pub(crate)` will be displayed as no visibility. + check( + r#"mod m { pub(crate) async unsafe extern "C" fn foo$0() {} }"#, + expect![[r#" + *foo* + + ```rust + test::m + ``` + + ```rust + pub(crate) async unsafe extern "C" fn foo() + ``` + "#]], + ); +} + +#[test] +fn test_hover_trait_show_qualifiers() { + check_actions( + r"unsafe trait foo$0() {}", + expect![[r#" + [ + Implementation( + FilePosition { + file_id: FileId( + 0, + ), + offset: 13, + }, + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_extern_crate() { + check( + r#" +//- /main.rs crate:main deps:std +extern crate st$0d; +//- /std/lib.rs crate:std +//! Standard library for this test +//! +//! Printed? +//! abc123 +"#, + expect![[r#" + *std* + + ```rust + extern crate std + ``` + + --- + + Standard library for this test + + Printed? + abc123 + "#]], + ); + check( + r#" +//- /main.rs crate:main deps:std +extern crate std as ab$0c; +//- /std/lib.rs crate:std +//! Standard library for this test +//! +//! Printed? +//! abc123 +"#, + expect![[r#" + *abc* + + ```rust + extern crate std + ``` + + --- + + Standard library for this test + + Printed? + abc123 + "#]], + ); +} + +#[test] +fn test_hover_mod_with_same_name_as_function() { + check( + r#" +use self::m$0y::Bar; +mod my { pub struct Bar; } + +fn my() {} +"#, + expect![[r#" + *my* + + ```rust + test + ``` + + ```rust + mod my + ``` + "#]], + ); +} + +#[test] +fn test_hover_struct_doc_comment() { + check( + r#" +/// 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##" + *Bar* + + ```rust + test + ``` + + ```rust + struct Bar + ``` + + --- + + This is an example + multiline doc + + # Example + + ``` + let five = 5; + + assert_eq!(6, my_crate::add_one(5)); + ``` + "##]], + ); +} + +#[test] +fn test_hover_struct_doc_attr() { + check( + r#" +#[doc = "bar docs"] +struct Bar; + +fn foo() { let bar = Ba$0r; } +"#, + expect![[r#" + *Bar* + + ```rust + test + ``` + + ```rust + struct Bar + ``` + + --- + + bar docs + "#]], + ); +} + +#[test] +fn test_hover_struct_doc_attr_multiple_and_mixed() { + check( + r#" +/// bar docs 0 +#[doc = "bar docs 1"] +#[doc = "bar docs 2"] +struct Bar; + +fn foo() { let bar = Ba$0r; } +"#, + expect![[r#" + *Bar* + + ```rust + test + ``` + + ```rust + struct Bar + ``` + + --- + + bar docs 0 + bar docs 1 + bar docs 2 + "#]], + ); +} + +#[test] +fn test_hover_external_url() { + check( + r#" +pub struct Foo; +/// [external](https://www.google.com) +pub struct B$0ar +"#, + expect![[r#" + *Bar* + + ```rust + test + ``` + + ```rust + pub struct Bar + ``` + + --- + + [external](https://www.google.com) + "#]], + ); +} + +// Check that we don't rewrite links which we can't identify +#[test] +fn test_hover_unknown_target() { + check( + r#" +pub struct Foo; +/// [baz](Baz) +pub struct B$0ar +"#, + expect![[r#" + *Bar* + + ```rust + test + ``` + + ```rust + pub struct Bar + ``` + + --- + + [baz](Baz) + "#]], + ); +} + +#[test] +fn test_hover_no_links() { + check_hover_no_links( + r#" +/// Test cases: +/// case 1. bare URL: https://www.example.com/ +/// case 2. inline URL with title: [example](https://www.example.com/) +/// case 3. code reference: [`Result`] +/// case 4. code reference but miss footnote: [`String`] +/// case 5. autolink: +/// case 6. email address: +/// case 7. reference: [example][example] +/// case 8. collapsed link: [example][] +/// case 9. shortcut link: [example] +/// case 10. inline without URL: [example]() +/// case 11. reference: [foo][foo] +/// case 12. reference: [foo][bar] +/// case 13. collapsed link: [foo][] +/// case 14. shortcut link: [foo] +/// case 15. inline without URL: [foo]() +/// case 16. just escaped text: \[foo] +/// case 17. inline link: [Foo](foo::Foo) +/// +/// [`Result`]: ../../std/result/enum.Result.html +/// [^example]: https://www.example.com/ +pub fn fo$0o() {} +"#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + pub fn foo() + ``` + + --- + + Test cases: + case 1. bare URL: https://www.example.com/ + case 2. inline URL with title: [example](https://www.example.com/) + case 3. code reference: `Result` + case 4. code reference but miss footnote: `String` + case 5. autolink: http://www.example.com/ + case 6. email address: test@example.com + case 7. reference: example + case 8. collapsed link: example + case 9. shortcut link: example + case 10. inline without URL: example + case 11. reference: foo + case 12. reference: foo + case 13. collapsed link: foo + case 14. shortcut link: foo + case 15. inline without URL: foo + case 16. just escaped text: \[foo\] + case 17. inline link: Foo + + [^example]: https://www.example.com/ + "#]], + ); +} + +#[test] +fn test_hover_macro_generated_struct_fn_doc_comment() { + cov_mark::check!(hover_macro_generated_struct_fn_doc_comment); + + check( + r#" +macro_rules! bar { + () => { + struct Bar; + impl Bar { + /// Do the foo + fn foo(&self) {} + } + } +} + +bar!(); + +fn foo() { let bar = Bar; bar.fo$0o(); } +"#, + expect![[r#" + *foo* + + ```rust + test::Bar + ``` + + ```rust + fn foo(&self) + ``` + + --- + + Do the foo + "#]], + ); +} + +#[test] +fn test_hover_macro_generated_struct_fn_doc_attr() { + cov_mark::check!(hover_macro_generated_struct_fn_doc_attr); + + check( + r#" +macro_rules! bar { + () => { + struct Bar; + impl Bar { + #[doc = "Do the foo"] + fn foo(&self) {} + } + } +} + +bar!(); + +fn foo() { let bar = Bar; bar.fo$0o(); } +"#, + expect![[r#" + *foo* + + ```rust + test::Bar + ``` + + ```rust + fn foo(&self) + ``` + + --- + + Do the foo + "#]], + ); +} + +#[test] +fn test_hover_trait_has_impl_action() { + check_actions( + r#"trait foo$0() {}"#, + expect![[r#" + [ + Implementation( + FilePosition { + file_id: FileId( + 0, + ), + offset: 6, + }, + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_struct_has_impl_action() { + check_actions( + r"struct foo$0() {}", + expect![[r#" + [ + Implementation( + FilePosition { + file_id: FileId( + 0, + ), + offset: 7, + }, + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_union_has_impl_action() { + check_actions( + r#"union foo$0() {}"#, + expect![[r#" + [ + Implementation( + FilePosition { + file_id: FileId( + 0, + ), + offset: 6, + }, + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_enum_has_impl_action() { + check_actions( + r"enum foo$0() { A, B }", + expect![[r#" + [ + Implementation( + FilePosition { + file_id: FileId( + 0, + ), + offset: 5, + }, + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_self_has_impl_action() { + check_actions( + r#"struct foo where Self$0:;"#, + expect![[r#" + [ + Implementation( + FilePosition { + file_id: FileId( + 0, + ), + offset: 7, + }, + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_test_has_action() { + check_actions( + r#" +#[test] +fn foo_$0test() {} +"#, + expect![[r#" + [ + Reference( + FilePosition { + file_id: FileId( + 0, + ), + offset: 11, + }, + ), + Runnable( + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..24, + focus_range: 11..19, + name: "foo_test", + kind: Function, + }, + kind: Test { + test_id: Path( + "foo_test", + ), + attr: TestAttr { + ignore: false, + }, + }, + cfg: None, + }, + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_test_mod_has_action() { + check_actions( + r#" +mod tests$0 { + #[test] + fn foo_test() {} +} +"#, + expect![[r#" + [ + Runnable( + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..46, + focus_range: 4..9, + name: "tests", + kind: Module, + description: "mod tests", + }, + kind: TestMod { + path: "tests", + }, + cfg: None, + }, + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_struct_has_goto_type_action() { + check_actions( + r#" +struct S{ f1: u32 } + +fn main() { let s$0t = S{ f1:0 }; } +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..19, + focus_range: 7..8, + name: "S", + kind: Struct, + description: "struct S", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_generic_struct_has_goto_type_actions() { + check_actions( + r#" +struct Arg(u32); +struct S{ f1: T } + +fn main() { let s$0t = S{ f1:Arg(0) }; } +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 17..37, + focus_range: 24..25, + name: "S", + kind: Struct, + description: "struct S", + }, + }, + HoverGotoTypeData { + mod_path: "test::Arg", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..16, + focus_range: 7..10, + name: "Arg", + kind: Struct, + description: "struct Arg", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_generic_struct_has_flattened_goto_type_actions() { + check_actions( + r#" +struct Arg(u32); +struct S{ f1: T } + +fn main() { let s$0t = S{ f1: S{ f1: Arg(0) } }; } +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 17..37, + focus_range: 24..25, + name: "S", + kind: Struct, + description: "struct S", + }, + }, + HoverGotoTypeData { + mod_path: "test::Arg", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..16, + focus_range: 7..10, + name: "Arg", + kind: Struct, + description: "struct Arg", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_tuple_has_goto_type_actions() { + check_actions( + r#" +struct A(u32); +struct B(u32); +mod M { + pub struct C(u32); +} + +fn main() { let s$0t = (A(1), B(2), M::C(3) ); } +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::A", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..14, + focus_range: 7..8, + name: "A", + kind: Struct, + description: "struct A", + }, + }, + HoverGotoTypeData { + mod_path: "test::B", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 15..29, + focus_range: 22..23, + name: "B", + kind: Struct, + description: "struct B", + }, + }, + HoverGotoTypeData { + mod_path: "test::M::C", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 42..60, + focus_range: 53..54, + name: "C", + kind: Struct, + description: "pub struct C", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_return_impl_trait_has_goto_type_action() { + check_actions( + r#" +trait Foo {} +fn foo() -> impl Foo {} + +fn main() { let s$0t = foo(); } +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..12, + focus_range: 6..9, + name: "Foo", + kind: Trait, + description: "trait Foo", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_generic_return_impl_trait_has_goto_type_action() { + check_actions( + r#" +trait Foo {} +struct S; +fn foo() -> impl Foo {} + +fn main() { let s$0t = foo(); } +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..15, + focus_range: 6..9, + name: "Foo", + kind: Trait, + description: "trait Foo", + }, + }, + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 16..25, + focus_range: 23..24, + name: "S", + kind: Struct, + description: "struct S", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_return_impl_traits_has_goto_type_action() { + check_actions( + r#" +trait Foo {} +trait Bar {} +fn foo() -> impl Foo + Bar {} + +fn main() { let s$0t = foo(); } +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..12, + focus_range: 6..9, + name: "Foo", + kind: Trait, + description: "trait Foo", + }, + }, + HoverGotoTypeData { + mod_path: "test::Bar", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 13..25, + focus_range: 19..22, + name: "Bar", + kind: Trait, + description: "trait Bar", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_generic_return_impl_traits_has_goto_type_action() { + check_actions( + r#" +trait Foo {} +trait Bar {} +struct S1 {} +struct S2 {} + +fn foo() -> impl Foo + Bar {} + +fn main() { let s$0t = foo(); } +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..15, + focus_range: 6..9, + name: "Foo", + kind: Trait, + description: "trait Foo", + }, + }, + HoverGotoTypeData { + mod_path: "test::Bar", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 16..31, + focus_range: 22..25, + name: "Bar", + kind: Trait, + description: "trait Bar", + }, + }, + HoverGotoTypeData { + mod_path: "test::S1", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 32..44, + focus_range: 39..41, + name: "S1", + kind: Struct, + description: "struct S1", + }, + }, + HoverGotoTypeData { + mod_path: "test::S2", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 45..57, + focus_range: 52..54, + name: "S2", + kind: Struct, + description: "struct S2", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_arg_impl_trait_has_goto_type_action() { + check_actions( + r#" +trait Foo {} +fn foo(ar$0g: &impl Foo) {} +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..12, + focus_range: 6..9, + name: "Foo", + kind: Trait, + description: "trait Foo", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_arg_impl_traits_has_goto_type_action() { + check_actions( + r#" +trait Foo {} +trait Bar {} +struct S{} + +fn foo(ar$0g: &impl Foo + Bar) {} +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..12, + focus_range: 6..9, + name: "Foo", + kind: Trait, + description: "trait Foo", + }, + }, + HoverGotoTypeData { + mod_path: "test::Bar", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 13..28, + focus_range: 19..22, + name: "Bar", + kind: Trait, + description: "trait Bar", + }, + }, + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 29..39, + focus_range: 36..37, + name: "S", + kind: Struct, + description: "struct S", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +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 }; +} +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "core::future::Future", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 254..436, + focus_range: 293..299, + name: "Future", + kind: Trait, + description: "pub trait Future", + }, + }, + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..9, + focus_range: 7..8, + name: "S", + kind: Struct, + description: "struct S", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_arg_generic_impl_trait_has_goto_type_action() { + check_actions( + r#" +trait Foo {} +struct S {} +fn foo(ar$0g: &impl Foo) {} +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..15, + focus_range: 6..9, + name: "Foo", + kind: Trait, + description: "trait Foo", + }, + }, + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 16..27, + focus_range: 23..24, + name: "S", + kind: Struct, + description: "struct S", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_dyn_return_has_goto_type_action() { + check_actions( + r#" +trait Foo {} +struct S; +impl Foo for S {} + +struct B{} +fn foo() -> B {} + +fn main() { let s$0t = foo(); } +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::B", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 42..55, + focus_range: 49..50, + name: "B", + kind: Struct, + description: "struct B", + }, + }, + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..12, + focus_range: 6..9, + name: "Foo", + kind: Trait, + description: "trait Foo", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_dyn_arg_has_goto_type_action() { + check_actions( + r#" +trait Foo {} +fn foo(ar$0g: &dyn Foo) {} +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..12, + focus_range: 6..9, + name: "Foo", + kind: Trait, + description: "trait Foo", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_generic_dyn_arg_has_goto_type_action() { + check_actions( + r#" +trait Foo {} +struct S {} +fn foo(ar$0g: &dyn Foo) {} +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..15, + focus_range: 6..9, + name: "Foo", + kind: Trait, + description: "trait Foo", + }, + }, + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 16..27, + focus_range: 23..24, + name: "S", + kind: Struct, + description: "struct S", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_goto_type_action_links_order() { + check_actions( + r#" +trait ImplTrait {} +trait DynTrait {} +struct B {} +struct S {} + +fn foo(a$0rg: &impl ImplTrait>>>) {} +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::ImplTrait", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..21, + focus_range: 6..15, + name: "ImplTrait", + kind: Trait, + description: "trait ImplTrait", + }, + }, + HoverGotoTypeData { + mod_path: "test::B", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 43..57, + focus_range: 50..51, + name: "B", + kind: Struct, + description: "struct B", + }, + }, + HoverGotoTypeData { + mod_path: "test::DynTrait", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 22..42, + focus_range: 28..36, + name: "DynTrait", + kind: Trait, + description: "trait DynTrait", + }, + }, + HoverGotoTypeData { + mod_path: "test::S", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 58..69, + focus_range: 65..66, + name: "S", + kind: Struct, + description: "struct S", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_associated_type_has_goto_type_action() { + check_actions( + r#" +trait Foo { + type Item; + fn get(self) -> Self::Item {} +} + +struct Bar{} +struct S{} + +impl Foo for S { type Item = Bar; } + +fn test() -> impl Foo { S {} } + +fn main() { let s$0t = test().get(); } +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..62, + focus_range: 6..9, + name: "Foo", + kind: Trait, + description: "trait Foo", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_const_param_has_goto_type_action() { + check_actions( + r#" +struct Bar; +struct Foo; + +impl Foo {} +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Bar", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..11, + focus_range: 7..10, + name: "Bar", + kind: Struct, + description: "struct Bar", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_type_param_has_goto_type_action() { + check_actions( + r#" +trait Foo {} + +fn foo(t: T$0){} +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..12, + focus_range: 6..9, + name: "Foo", + kind: Trait, + description: "trait Foo", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn test_hover_self_has_go_to_type() { + check_actions( + r#" +struct Foo; +impl Foo { + fn foo(&self$0) {} +} +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..11, + focus_range: 7..10, + name: "Foo", + kind: Struct, + description: "struct Foo", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn hover_displays_normalized_crate_names() { + check( + r#" +//- /lib.rs crate:name-with-dashes +pub mod wrapper { + pub struct Thing { x: u32 } + + impl Thing { + pub fn new() -> Thing { Thing { x: 0 } } + } +} + +//- /main.rs crate:main deps:name-with-dashes +fn main() { let foo_test = name_with_dashes::wrapper::Thing::new$0(); } +"#, + expect![[r#" + *new* + + ```rust + name_with_dashes::wrapper::Thing + ``` + + ```rust + pub fn new() -> Thing + ``` + "#]], + ) +} + +#[test] +fn hover_field_pat_shorthand_ref_match_ergonomics() { + check( + r#" +struct S { + f: i32, +} + +fn main() { + let s = S { f: 0 }; + let S { f$0 } = &s; +} +"#, + expect![[r#" + *f* + + ```rust + f: &i32 + ``` + --- + + ```rust + test::S + ``` + + ```rust + f: i32 + ``` + "#]], + ); +} + +#[test] +fn hover_self_param_shows_type() { + check( + r#" +struct Foo {} +impl Foo { + fn bar(&sel$0f) {} +} +"#, + expect![[r#" + *self* + + ```rust + self: &Foo + ``` + "#]], + ); +} + +#[test] +fn hover_self_param_shows_type_for_arbitrary_self_type() { + check( + r#" +struct Arc(T); +struct Foo {} +impl Foo { + fn bar(sel$0f: Arc) {} +} +"#, + expect![[r#" + *self* + + ```rust + self: Arc + ``` + "#]], + ); +} + +#[test] +fn hover_doc_outer_inner() { + check( + r#" +/// Be quick; +mod Foo$0 { + //! time is mana + + /// This comment belongs to the function + fn foo() {} +} +"#, + expect![[r#" + *Foo* + + ```rust + test + ``` + + ```rust + mod Foo + ``` + + --- + + Be quick; + time is mana + "#]], + ); +} + +#[test] +fn hover_doc_outer_inner_attribue() { + check( + r#" +#[doc = "Be quick;"] +mod Foo$0 { + #![doc = "time is mana"] + + #[doc = "This comment belongs to the function"] + fn foo() {} +} +"#, + expect![[r#" + *Foo* + + ```rust + test + ``` + + ```rust + mod Foo + ``` + + --- + + Be quick; + time is mana + "#]], + ); +} + +#[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() { + // no$0hover +} +"#, + ); +} + +#[test] +fn hover_label() { + check( + r#" +fn foo() { + 'label$0: loop {} +} +"#, + expect![[r#" + *'label* + + ```rust + 'label + ``` + "#]], + ); +} + +#[test] +fn hover_lifetime() { + check( + r#"fn foo<'lifetime>(_: &'lifetime$0 ()) {}"#, + expect![[r#" + *'lifetime* + + ```rust + 'lifetime + ``` + "#]], + ); +} + +#[test] +fn hover_type_param() { + check( + r#" +//- minicore: sized +struct Foo(T); +trait TraitA {} +trait TraitB {} +impl Foo where T: Sized {} +"#, + expect![[r#" + *T* + + ```rust + T: TraitA + TraitB + ``` + "#]], + ); + check( + r#" +//- minicore: sized +struct Foo(T); +impl Foo {} +"#, + expect![[r#" + *T* + + ```rust + T + ``` + "#]], + ); + // lifetimes bounds arent being tracked yet + check( + r#" +//- minicore: sized +struct Foo(T); +impl Foo {} +"#, + expect![[r#" + *T* + + ```rust + T + ``` + "#]], + ); +} + +#[test] +fn hover_type_param_sized_bounds() { + // implicit `: Sized` bound + check( + r#" +//- minicore: sized +trait Trait {} +struct Foo(T); +impl Foo {} +"#, + expect![[r#" + *T* + + ```rust + T: Trait + ``` + "#]], + ); + check( + r#" +//- minicore: sized +trait Trait {} +struct Foo(T); +impl Foo {} +"#, + expect![[r#" + *T* + + ```rust + T: Trait + ?Sized + ``` + "#]], + ); +} + +mod type_param_sized_bounds { + use super::*; + + #[test] + fn single_implicit() { + check( + r#" +//- minicore: sized +fn foo() {} +"#, + expect![[r#" + *T* + + ```rust + T + ``` + "#]], + ); + } + + #[test] + fn single_explicit() { + check( + r#" +//- minicore: sized +fn foo() {} +"#, + expect![[r#" + *T* + + ```rust + T + ``` + "#]], + ); + } + + #[test] + fn single_relaxed() { + check( + r#" +//- minicore: sized +fn foo() {} +"#, + expect![[r#" + *T* + + ```rust + T: ?Sized + ``` + "#]], + ); + } + + #[test] + fn multiple_implicit() { + check( + r#" +//- minicore: sized +trait Trait {} +fn foo() {} +"#, + expect![[r#" + *T* + + ```rust + T: Trait + ``` + "#]], + ); + } + + #[test] + fn multiple_explicit() { + check( + r#" +//- minicore: sized +trait Trait {} +fn foo() {} +"#, + expect![[r#" + *T* + + ```rust + T: Trait + ``` + "#]], + ); + } + + #[test] + fn multiple_relaxed() { + check( + r#" +//- minicore: sized +trait Trait {} +fn foo() {} +"#, + expect![[r#" + *T* + + ```rust + T: Trait + ?Sized + ``` + "#]], + ); + } + + #[test] + fn mixed() { + check( + r#" +//- minicore: sized +fn foo() {} +"#, + expect![[r#" + *T* + + ```rust + T + ``` + "#]], + ); + check( + r#" +//- minicore: sized +trait Trait {} +fn foo() {} +"#, + expect![[r#" + *T* + + ```rust + T: Trait + ``` + "#]], + ); + } +} + +#[test] +fn hover_const_param() { + check( + r#" +struct Foo; +impl Foo {} +"#, + expect![[r#" + *LEN* + + ```rust + const LEN: usize + ``` + "#]], + ); +} + +#[test] +fn hover_const_pat() { + check( + r#" +/// This is a doc +const FOO: usize = 3; +fn foo() { + match 5 { + FOO$0 => (), + _ => () + } +} +"#, + expect![[r#" + *FOO* + + ```rust + test + ``` + + ```rust + const FOO: usize + ``` + + --- + + This is a doc + "#]], + ); +} + +#[test] +fn hover_mod_def() { + check( + r#" +//- /main.rs +mod foo$0; +//- /foo.rs +//! For the horde! +"#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + mod foo + ``` + + --- + + For the horde! + "#]], + ); +} + +#[test] +fn hover_self_in_use() { + check( + r#" +//! This should not appear +mod foo { + /// But this should appear + pub mod bar {} +} +use foo::bar::{self$0}; +"#, + expect![[r#" + *self* + + ```rust + test::foo + ``` + + ```rust + mod bar + ``` + + --- + + But this should appear + "#]], + ) +} + +#[test] +fn hover_keyword() { + check( + 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* + + ```rust + return + ``` + + --- + + Docs for return_keyword + "#]], + ); +} + +#[test] +fn hover_builtin() { + check( + r#" +//- /main.rs crate:main deps:std +cosnt _: &str$0 = ""; } + +//- /libstd.rs crate:std +/// Docs for prim_str +mod prim_str {} +"#, + expect![[r#" + *str* + + ```rust + str + ``` + + --- + + Docs for prim_str + "#]], + ); +} + +#[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() where T::Assoc$0: {} + +trait A { + type Assoc; +}"#, + expect![[r#" + *Assoc* + + ```rust + test + ``` + + ```rust + type Assoc + ``` + "#]], + ); + check( + r#" +fn foo() { + let _: ::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. + "#]], + ) +} + +#[test] +fn hover_attr_path_qualifier() { + cov_mark::check!(name_ref_classify_attr_path_qualifier); + check( + r#" +//- /foo.rs crate:foo + +//- /lib.rs crate:main.rs deps:foo +#[fo$0o::bar()] +struct Foo; +"#, + expect![[r#" + *foo* + + ```rust + extern crate foo + ``` + "#]], + ) +} + +#[test] +fn hover_rename() { + check( + r#" +use self as foo$0; +"#, + expect![[r#" + *foo* + + ```rust + extern crate test + ``` + "#]], + ); + check( + r#" +mod bar {} +use bar::{self as foo$0}; +"#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + mod bar + ``` + "#]], + ); + check( + r#" +mod bar { + use super as foo$0; +} +"#, + expect![[r#" + *foo* + + ```rust + extern crate test + ``` + "#]], + ); + check( + r#" +use crate as foo$0; +"#, + expect![[r#" + *foo* + + ```rust + extern crate test + ``` + "#]], + ); +} + +#[test] +fn hover_attribute_in_macro() { + check( + r#" +macro_rules! identity { + ($struct:item) => { + $struct + }; +} +#[rustc_builtin_macro] +pub macro Copy {} +identity!{ + #[derive(Copy$0)] + struct Foo; +} +"#, + expect![[r#" + *Copy* + + ```rust + test + ``` + + ```rust + pub macro Copy + ``` + "#]], + ); +} + +#[test] +fn hover_derive_input() { + check( + r#" +#[rustc_builtin_macro] +pub macro Copy {} +#[derive(Copy$0)] +struct Foo; +"#, + expect![[r#" + *Copy* + + ```rust + test + ``` + + ```rust + pub macro Copy + ``` + "#]], + ); + check( + r#" +mod foo { + #[rustc_builtin_macro] + pub macro Copy {} +} +#[derive(foo::Copy$0)] +struct Foo; +"#, + expect![[r#" + *Copy* + + ```rust + test + ``` + + ```rust + pub macro Copy + ``` + "#]], + ); +} + +#[test] +fn hover_range_math() { + check_hover_range( + r#" +fn f() { let expr = $01 + 2 * 3$0 } +"#, + expect![[r#" + ```rust + i32 + ```"#]], + ); + + check_hover_range( + r#" +fn f() { let expr = 1 $0+ 2 * $03 } +"#, + expect![[r#" + ```rust + i32 + ```"#]], + ); + + check_hover_range( + r#" +fn f() { let expr = 1 + $02 * 3$0 } +"#, + expect![[r#" + ```rust + i32 + ```"#]], + ); +} + +#[test] +fn hover_range_arrays() { + check_hover_range( + r#" +fn f() { let expr = $0[1, 2, 3, 4]$0 } +"#, + expect![[r#" + ```rust + [i32; 4] + ```"#]], + ); + + check_hover_range( + r#" +fn f() { let expr = [1, 2, $03, 4]$0 } +"#, + expect![[r#" + ```rust + [i32; 4] + ```"#]], + ); + + check_hover_range( + r#" +fn f() { let expr = [1, 2, $03$0, 4] } +"#, + expect![[r#" + ```rust + i32 + ```"#]], + ); +} + +#[test] +fn hover_range_functions() { + check_hover_range( + r#" +fn f(a: &[T]) { } +fn b() { $0f$0(&[1, 2, 3, 4, 5]); } +"#, + expect![[r#" + ```rust + fn f(&[i32]) + ```"#]], + ); + + check_hover_range( + r#" +fn f(a: &[T]) { } +fn b() { f($0&[1, 2, 3, 4, 5]$0); } +"#, + expect![[r#" + ```rust + &[i32; 5] + ```"#]], + ); +} + +#[test] +fn hover_range_shows_nothing_when_invalid() { + check_hover_range_no_results( + r#" +fn f(a: &[T]) { } +fn b()$0 { f(&[1, 2, 3, 4, 5]); }$0 +"#, + ); + + check_hover_range_no_results( + r#" +fn f$0(a: &[T]) { } +fn b() { f(&[1, 2, 3,$0 4, 5]); } +"#, + ); + + check_hover_range_no_results( + r#" +fn $0f() { let expr = [1, 2, 3, 4]$0 } +"#, + ); +} + +#[test] +fn hover_range_shows_unit_for_statements() { + check_hover_range( + r#" +fn f(a: &[T]) { } +fn b() { $0f(&[1, 2, 3, 4, 5]); }$0 +"#, + expect![[r#" + ```rust + () + ```"#]], + ); + + check_hover_range( + r#" +fn f() { let expr$0 = $0[1, 2, 3, 4] } +"#, + expect![[r#" + ```rust + () + ```"#]], + ); +} + +#[test] +fn hover_range_for_pat() { + check_hover_range( + r#" +fn foo() { + let $0x$0 = 0; +} +"#, + expect![[r#" + ```rust + i32 + ```"#]], + ); + + check_hover_range( + r#" +fn foo() { + let $0x$0 = ""; +} +"#, + expect![[r#" + ```rust + &str + ```"#]], + ); +} + +#[test] +fn hover_range_shows_coercions_if_applicable_expr() { + check_hover_range( + r#" +fn foo() { + let x: &u32 = $0&&&&&0$0; +} +"#, + expect![[r#" + ```text + Type: &&&&&u32 + Coerced to: &u32 + ``` + "#]], + ); + check_hover_range( + r#" +fn foo() { + let x: *const u32 = $0&0$0; +} +"#, + expect![[r#" + ```text + Type: &u32 + Coerced to: *const u32 + ``` + "#]], + ); +} + +#[test] +fn hover_range_shows_type_actions() { + check_actions( + r#" +struct Foo; +fn foo() { + let x: &Foo = $0&&&&&Foo$0; +} +"#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Foo", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..11, + focus_range: 7..10, + name: "Foo", + kind: Struct, + description: "struct Foo", + }, + }, + ], + ), + ] + "#]], + ); +} + +#[test] +fn hover_try_expr_res() { + check_hover_range( + r#" +//- minicore:result +struct FooError; + +fn foo() -> Result<(), FooError> { + Ok($0Result::<(), FooError>::Ok(())?$0) +} +"#, + expect![[r#" + ```rust + () + ```"#]], + ); + check_hover_range( + r#" +//- minicore:result +struct FooError; +struct BarError; + +fn foo() -> Result<(), FooError> { + Ok($0Result::<(), BarError>::Ok(())?$0) +} +"#, + expect![[r#" + ```text + Try Error Type: BarError + Propagated as: FooError + ``` + "#]], + ); +} + +#[test] +fn hover_try_expr() { + check_hover_range( + r#" +struct NotResult(T, U); +struct Short; +struct Looooong; + +fn foo() -> NotResult<(), Looooong> { + $0NotResult((), Short)?$0; +} +"#, + expect![[r#" + ```text + Try Target Type: NotResult<(), Short> + Propagated as: NotResult<(), Looooong> + ``` + "#]], + ); + check_hover_range( + r#" +struct NotResult(T, U); +struct Short; +struct Looooong; + +fn foo() -> NotResult<(), Short> { + $0NotResult((), Looooong)?$0; +} +"#, + expect![[r#" + ```text + Try Target Type: NotResult<(), Looooong> + Propagated as: NotResult<(), Short> + ``` + "#]], + ); +} + +#[test] +fn hover_try_expr_option() { + cov_mark::check!(hover_try_expr_opt_opt); + check_hover_range( + r#" +//- minicore: option, try + +fn foo() -> Option<()> { + $0Some(0)?$0; + None +} +"#, + expect![[r#" + ```rust + as Try>::Output + ```"#]], + ); +} + +#[test] +fn hover_deref_expr() { + check_hover_range( + r#" +//- minicore: deref +use core::ops::Deref; + +struct DerefExample { + value: T +} + +impl Deref for DerefExample { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.value + } +} + +fn foo() { + let x = DerefExample { value: 0 }; + let y: i32 = $0*x$0; +} +"#, + expect![[r#" + ```text + Dereferenced from: DerefExample + To type: i32 + ``` + "#]], + ); +} + +#[test] +fn hover_deref_expr_with_coercion() { + check_hover_range( + r#" +//- minicore: deref +use core::ops::Deref; + +struct DerefExample { + value: T +} + +impl Deref for DerefExample { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.value + } +} + +fn foo() { + let x = DerefExample { value: &&&&&0 }; + let y: &i32 = $0*x$0; +} +"#, + expect![[r#" + ```text + Dereferenced from: DerefExample<&&&&&i32> + To type: &&&&&i32 + Coerced to: &i32 + ``` + "#]], + ); +} + +#[test] +fn hover_intra_in_macro() { + check( + r#" +macro_rules! foo_macro { + ($(#[$attr:meta])* $name:ident) => { + $(#[$attr])* + pub struct $name; + } +} + +foo_macro!( + /// Doc comment for [`Foo$0`] + Foo +); +"#, + expect![[r#" + *[`Foo`]* + + ```rust + test + ``` + + ```rust + pub struct Foo + ``` + + --- + + Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html) + "#]], + ); +} + +#[test] +fn hover_intra_in_attr() { + check( + r#" +#[doc = "Doc comment for [`Foo$0`]"] +pub struct Foo; +"#, + expect![[r#" + *[`Foo`]* + + ```rust + test + ``` + + ```rust + pub struct Foo + ``` + + --- + + Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html) + "#]], + ); +} diff --git a/crates/rust-analyzer/tests/slow-tests/tidy.rs b/crates/rust-analyzer/tests/slow-tests/tidy.rs index e98f207c1d..5709f34ddd 100644 --- a/crates/rust-analyzer/tests/slow-tests/tidy.rs +++ b/crates/rust-analyzer/tests/slow-tests/tidy.rs @@ -191,7 +191,7 @@ fn deny_clippy(path: &Path, text: &str) { // The documentation in string literals may contain anything for its own purposes "ide_db/src/helpers/generated_lints.rs", // The tests test clippy lint hovers - "ide/src/hover.rs", + "ide/src/hover/tests.rs", ]; if ignore.iter().any(|p| path.ends_with(p)) { return;