mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-29 05:15:04 +00:00
Rewrite doctest runnables
Handle more cases in a generic way without copy-pasting code.
This commit is contained in:
parent
0da1532ef2
commit
c888f1de6f
2 changed files with 82 additions and 106 deletions
|
@ -117,25 +117,6 @@ impl NavigationTarget {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allows `NavigationTarget` to be created from a `DocCommentsOwner` and a `NameOwner`
|
|
||||||
pub(crate) fn from_doc_commented(
|
|
||||||
db: &RootDatabase,
|
|
||||||
named: InFile<&dyn ast::NameOwner>,
|
|
||||||
node: InFile<&dyn ast::DocCommentsOwner>,
|
|
||||||
) -> NavigationTarget {
|
|
||||||
let name =
|
|
||||||
named.value.name().map(|it| it.text().clone()).unwrap_or_else(|| SmolStr::new("_"));
|
|
||||||
let frange = node.map(|it| it.syntax()).original_file_range(db);
|
|
||||||
|
|
||||||
NavigationTarget::from_syntax(
|
|
||||||
frange.file_id,
|
|
||||||
name,
|
|
||||||
None,
|
|
||||||
frange.range,
|
|
||||||
node.value.syntax().kind(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_syntax(
|
fn from_syntax(
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
name: SmolStr,
|
name: SmolStr,
|
||||||
|
|
|
@ -10,7 +10,10 @@ use syntax::{
|
||||||
match_ast, SyntaxNode,
|
match_ast, SyntaxNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{display::ToNav, FileId, NavigationTarget};
|
use crate::{
|
||||||
|
display::{ToNav, TryToNav},
|
||||||
|
FileId, NavigationTarget,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Runnable {
|
pub struct Runnable {
|
||||||
|
@ -101,117 +104,109 @@ pub(crate) fn runnable(
|
||||||
item: SyntaxNode,
|
item: SyntaxNode,
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
) -> Option<Runnable> {
|
) -> Option<Runnable> {
|
||||||
match_ast! {
|
let runnable_item = match_ast! {
|
||||||
match item {
|
match (item.clone()) {
|
||||||
ast::Struct(it) => runnable_struct(sema, it, file_id),
|
|
||||||
ast::Fn(it) => runnable_fn(sema, it, file_id),
|
ast::Fn(it) => runnable_fn(sema, it, file_id),
|
||||||
ast::Module(it) => runnable_mod(sema, it),
|
ast::Module(it) => runnable_mod(sema, it),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
runnable_item.or_else(|| runnable_doctest(sema, item))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runnable_fn(sema: &Semantics<RootDatabase>, func: ast::Fn, file_id: FileId) -> Option<Runnable> {
|
fn runnable_fn(sema: &Semantics<RootDatabase>, func: ast::Fn, file_id: FileId) -> Option<Runnable> {
|
||||||
let def = sema.to_def(&func)?;
|
let def = sema.to_def(&func)?;
|
||||||
let name_string = func.name()?.text().to_string();
|
let name_string = func.name()?.text().to_string();
|
||||||
|
|
||||||
let attrs = def.attrs(sema.db);
|
|
||||||
let kind = if name_string == "main" {
|
let kind = if name_string == "main" {
|
||||||
RunnableKind::Bin
|
RunnableKind::Bin
|
||||||
} else {
|
} else {
|
||||||
let test_id = match sema.to_def(&func).map(|def| def.module(sema.db)) {
|
let canonical_path = sema.to_def(&func).and_then(|def| {
|
||||||
Some(module) => {
|
let def: hir::ModuleDef = def.into();
|
||||||
let def = sema.to_def(&func)?;
|
def.canonical_path(sema.db)
|
||||||
let impl_trait_name = def.as_assoc_item(sema.db).and_then(|assoc_item| {
|
});
|
||||||
match assoc_item.container(sema.db) {
|
let test_id = canonical_path.map(TestId::Path).unwrap_or(TestId::Name(name_string));
|
||||||
hir::AssocItemContainer::Trait(trait_item) => {
|
|
||||||
Some(trait_item.name(sema.db).to_string())
|
|
||||||
}
|
|
||||||
hir::AssocItemContainer::Impl(impl_def) => impl_def
|
|
||||||
.target_ty(sema.db)
|
|
||||||
.as_adt()
|
|
||||||
.map(|adt| adt.name(sema.db).to_string()),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let path_iter = module
|
|
||||||
.path_to_root(sema.db)
|
|
||||||
.into_iter()
|
|
||||||
.rev()
|
|
||||||
.filter_map(|it| it.name(sema.db))
|
|
||||||
.map(|name| name.to_string());
|
|
||||||
|
|
||||||
let path = if let Some(impl_trait_name) = impl_trait_name {
|
|
||||||
path_iter
|
|
||||||
.chain(std::iter::once(impl_trait_name))
|
|
||||||
.chain(std::iter::once(name_string))
|
|
||||||
.join("::")
|
|
||||||
} else {
|
|
||||||
path_iter.chain(std::iter::once(name_string)).join("::")
|
|
||||||
};
|
|
||||||
|
|
||||||
TestId::Path(path)
|
|
||||||
}
|
|
||||||
None => TestId::Name(name_string),
|
|
||||||
};
|
|
||||||
|
|
||||||
if test_related_attribute(&func).is_some() {
|
if test_related_attribute(&func).is_some() {
|
||||||
let attr = TestAttr::from_fn(&func);
|
let attr = TestAttr::from_fn(&func);
|
||||||
RunnableKind::Test { test_id, attr }
|
RunnableKind::Test { test_id, attr }
|
||||||
} else if func.has_atom_attr("bench") {
|
} else if func.has_atom_attr("bench") {
|
||||||
RunnableKind::Bench { test_id }
|
RunnableKind::Bench { test_id }
|
||||||
} else if has_runnable_doc_test(&attrs) {
|
|
||||||
RunnableKind::DocTest { test_id }
|
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let nav = if let RunnableKind::DocTest { .. } = kind {
|
let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &func));
|
||||||
NavigationTarget::from_doc_commented(
|
let cfg = def.attrs(sema.db).cfg();
|
||||||
sema.db,
|
Some(Runnable { nav, kind, cfg })
|
||||||
InFile::new(file_id.into(), &func),
|
|
||||||
InFile::new(file_id.into(), &func),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &func))
|
|
||||||
};
|
|
||||||
Some(Runnable { nav, kind, cfg: attrs.cfg() })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runnable_struct(
|
fn runnable_doctest(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> {
|
||||||
sema: &Semantics<RootDatabase>,
|
match_ast! {
|
||||||
strukt: ast::Struct,
|
match item {
|
||||||
file_id: FileId,
|
ast::Fn(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
|
||||||
) -> Option<Runnable> {
|
ast::Struct(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
|
||||||
let def = sema.to_def(&strukt)?;
|
ast::Enum(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
|
||||||
let name_string = strukt.name()?.text().to_string();
|
ast::Union(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
|
||||||
|
ast::Trait(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
|
||||||
|
ast::Const(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
|
||||||
|
ast::Static(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
|
||||||
|
ast::TypeAlias(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let attrs = def.attrs(sema.db);
|
fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> {
|
||||||
|
let attrs = match def {
|
||||||
|
hir::ModuleDef::Module(it) => it.attrs(sema.db),
|
||||||
|
hir::ModuleDef::Function(it) => it.attrs(sema.db),
|
||||||
|
hir::ModuleDef::Adt(it) => it.attrs(sema.db),
|
||||||
|
hir::ModuleDef::EnumVariant(it) => it.attrs(sema.db),
|
||||||
|
hir::ModuleDef::Const(it) => it.attrs(sema.db),
|
||||||
|
hir::ModuleDef::Static(it) => it.attrs(sema.db),
|
||||||
|
hir::ModuleDef::Trait(it) => it.attrs(sema.db),
|
||||||
|
hir::ModuleDef::TypeAlias(it) => it.attrs(sema.db),
|
||||||
|
hir::ModuleDef::BuiltinType(_) => return None,
|
||||||
|
};
|
||||||
if !has_runnable_doc_test(&attrs) {
|
if !has_runnable_doc_test(&attrs) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let test_id = match sema.to_def(&strukt).map(|def| def.module(sema.db)) {
|
let def_name = def.name(sema.db).map(|it| it.to_string());
|
||||||
Some(module) => {
|
let test_id = def
|
||||||
let path_iter = module
|
.canonical_path(sema.db)
|
||||||
.path_to_root(sema.db)
|
// This probably belongs to canonical path?
|
||||||
.into_iter()
|
.map(|path| {
|
||||||
.rev()
|
let assoc_def = match def {
|
||||||
.filter_map(|it| it.name(sema.db))
|
hir::ModuleDef::Function(it) => it.as_assoc_item(sema.db),
|
||||||
.map(|name| name.to_string());
|
hir::ModuleDef::Const(it) => it.as_assoc_item(sema.db),
|
||||||
let path = path_iter.chain(std::iter::once(name_string)).join("::");
|
hir::ModuleDef::TypeAlias(it) => it.as_assoc_item(sema.db),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
// FIXME: this also looks very wrong
|
||||||
|
if let Some(assoc_def) = assoc_def {
|
||||||
|
if let hir::AssocItemContainer::Impl(imp) = assoc_def.container(sema.db) {
|
||||||
|
if let Some(adt) = imp.target_ty(sema.db).as_adt() {
|
||||||
|
let name = adt.name(sema.db).to_string();
|
||||||
|
let idx = path.rfind(':').unwrap_or(0);
|
||||||
|
let (prefix, suffix) = path.split_at(idx);
|
||||||
|
return format!("{}{}::{}", prefix, name, suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path
|
||||||
|
})
|
||||||
|
.map(TestId::Path)
|
||||||
|
.or_else(|| def_name.clone().map(TestId::Name))?;
|
||||||
|
|
||||||
TestId::Path(path)
|
let mut nav = def.try_to_nav(sema.db)?;
|
||||||
}
|
nav.focus_range = None;
|
||||||
None => TestId::Name(name_string),
|
nav.description = None;
|
||||||
};
|
nav.docs = None;
|
||||||
|
nav.kind = syntax::SyntaxKind::COMMENT;
|
||||||
let nav = NavigationTarget::from_doc_commented(
|
let res = Runnable { nav, kind: RunnableKind::DocTest { test_id }, cfg: attrs.cfg() };
|
||||||
sema.db,
|
Some(res)
|
||||||
InFile::new(file_id.into(), &strukt),
|
|
||||||
InFile::new(file_id.into(), &strukt),
|
|
||||||
);
|
|
||||||
Some(Runnable { nav, kind: RunnableKind::DocTest { test_id }, cfg: attrs.cfg() })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
@ -309,7 +304,7 @@ mod tests {
|
||||||
|
|
||||||
use crate::fixture;
|
use crate::fixture;
|
||||||
|
|
||||||
use super::{RunnableAction, BENCH, BIN, DOCTEST, TEST};
|
use super::*;
|
||||||
|
|
||||||
fn check(
|
fn check(
|
||||||
ra_fixture: &str,
|
ra_fixture: &str,
|
||||||
|
@ -538,7 +533,7 @@ struct StructWithRunnable(String);
|
||||||
full_range: 15..74,
|
full_range: 15..74,
|
||||||
focus_range: None,
|
focus_range: None,
|
||||||
name: "should_have_runnable",
|
name: "should_have_runnable",
|
||||||
kind: FN,
|
kind: COMMENT,
|
||||||
container_name: None,
|
container_name: None,
|
||||||
description: None,
|
description: None,
|
||||||
docs: None,
|
docs: None,
|
||||||
|
@ -558,7 +553,7 @@ struct StructWithRunnable(String);
|
||||||
full_range: 76..148,
|
full_range: 76..148,
|
||||||
focus_range: None,
|
focus_range: None,
|
||||||
name: "should_have_runnable_1",
|
name: "should_have_runnable_1",
|
||||||
kind: FN,
|
kind: COMMENT,
|
||||||
container_name: None,
|
container_name: None,
|
||||||
description: None,
|
description: None,
|
||||||
docs: None,
|
docs: None,
|
||||||
|
@ -578,7 +573,7 @@ struct StructWithRunnable(String);
|
||||||
full_range: 150..254,
|
full_range: 150..254,
|
||||||
focus_range: None,
|
focus_range: None,
|
||||||
name: "should_have_runnable_2",
|
name: "should_have_runnable_2",
|
||||||
kind: FN,
|
kind: COMMENT,
|
||||||
container_name: None,
|
container_name: None,
|
||||||
description: None,
|
description: None,
|
||||||
docs: None,
|
docs: None,
|
||||||
|
@ -598,7 +593,7 @@ struct StructWithRunnable(String);
|
||||||
full_range: 756..821,
|
full_range: 756..821,
|
||||||
focus_range: None,
|
focus_range: None,
|
||||||
name: "StructWithRunnable",
|
name: "StructWithRunnable",
|
||||||
kind: STRUCT,
|
kind: COMMENT,
|
||||||
container_name: None,
|
container_name: None,
|
||||||
description: None,
|
description: None,
|
||||||
docs: None,
|
docs: None,
|
||||||
|
@ -660,7 +655,7 @@ impl Data {
|
||||||
full_range: 44..98,
|
full_range: 44..98,
|
||||||
focus_range: None,
|
focus_range: None,
|
||||||
name: "foo",
|
name: "foo",
|
||||||
kind: FN,
|
kind: COMMENT,
|
||||||
container_name: None,
|
container_name: None,
|
||||||
description: None,
|
description: None,
|
||||||
docs: None,
|
docs: None,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue