aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-12-17 16:29:31 +0000
committerAleksey Kladov <[email protected]>2020-12-17 17:11:40 +0000
commitc888f1de6fa93d85e0fd662efd5a85421e86b326 (patch)
tree711d8244ad54a433b5102008d8a46779f0dcc55d /crates/ide
parent0da1532ef2ecda96612a5ccc326b8c6c070106d6 (diff)
Rewrite doctest runnables
Handle more cases in a generic way without copy-pasting code.
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/display/navigation_target.rs19
-rw-r--r--crates/ide/src/runnables.rs171
2 files changed, 83 insertions, 107 deletions
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs
index 8410bf5a2..522607cb7 100644
--- a/crates/ide/src/display/navigation_target.rs
+++ b/crates/ide/src/display/navigation_target.rs
@@ -117,25 +117,6 @@ impl NavigationTarget {
117 ) 117 )
118 } 118 }
119 119
120 /// Allows `NavigationTarget` to be created from a `DocCommentsOwner` and a `NameOwner`
121 pub(crate) fn from_doc_commented(
122 db: &RootDatabase,
123 named: InFile<&dyn ast::NameOwner>,
124 node: InFile<&dyn ast::DocCommentsOwner>,
125 ) -> NavigationTarget {
126 let name =
127 named.value.name().map(|it| it.text().clone()).unwrap_or_else(|| SmolStr::new("_"));
128 let frange = node.map(|it| it.syntax()).original_file_range(db);
129
130 NavigationTarget::from_syntax(
131 frange.file_id,
132 name,
133 None,
134 frange.range,
135 node.value.syntax().kind(),
136 )
137 }
138
139 fn from_syntax( 120 fn from_syntax(
140 file_id: FileId, 121 file_id: FileId,
141 name: SmolStr, 122 name: SmolStr,
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 75a2358fa..2f465c195 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -10,7 +10,10 @@ use syntax::{
10 match_ast, SyntaxNode, 10 match_ast, SyntaxNode,
11}; 11};
12 12
13use crate::{display::ToNav, FileId, NavigationTarget}; 13use crate::{
14 display::{ToNav, TryToNav},
15 FileId, NavigationTarget,
16};
14 17
15#[derive(Debug, Clone)] 18#[derive(Debug, Clone)]
16pub struct Runnable { 19pub struct Runnable {
@@ -101,117 +104,109 @@ pub(crate) fn runnable(
101 item: SyntaxNode, 104 item: SyntaxNode,
102 file_id: FileId, 105 file_id: FileId,
103) -> Option<Runnable> { 106) -> Option<Runnable> {
104 match_ast! { 107 let runnable_item = match_ast! {
105 match item { 108 match (item.clone()) {
106 ast::Struct(it) => runnable_struct(sema, it, file_id),
107 ast::Fn(it) => runnable_fn(sema, it, file_id), 109 ast::Fn(it) => runnable_fn(sema, it, file_id),
108 ast::Module(it) => runnable_mod(sema, it), 110 ast::Module(it) => runnable_mod(sema, it),
109 _ => None, 111 _ => None,
110 } 112 }
111 } 113 };
114 runnable_item.or_else(|| runnable_doctest(sema, item))
112} 115}
113 116
114fn runnable_fn(sema: &Semantics<RootDatabase>, func: ast::Fn, file_id: FileId) -> Option<Runnable> { 117fn runnable_fn(sema: &Semantics<RootDatabase>, func: ast::Fn, file_id: FileId) -> Option<Runnable> {
115 let def = sema.to_def(&func)?; 118 let def = sema.to_def(&func)?;
116 let name_string = func.name()?.text().to_string(); 119 let name_string = func.name()?.text().to_string();
117 120
118 let attrs = def.attrs(sema.db);
119 let kind = if name_string == "main" { 121 let kind = if name_string == "main" {
120 RunnableKind::Bin 122 RunnableKind::Bin
121 } else { 123 } else {
122 let test_id = match sema.to_def(&func).map(|def| def.module(sema.db)) { 124 let canonical_path = sema.to_def(&func).and_then(|def| {
123 Some(module) => { 125 let def: hir::ModuleDef = def.into();
124 let def = sema.to_def(&func)?; 126 def.canonical_path(sema.db)
125 let impl_trait_name = def.as_assoc_item(sema.db).and_then(|assoc_item| { 127 });
126 match assoc_item.container(sema.db) { 128 let test_id = canonical_path.map(TestId::Path).unwrap_or(TestId::Name(name_string));
127 hir::AssocItemContainer::Trait(trait_item) => {
128 Some(trait_item.name(sema.db).to_string())
129 }
130 hir::AssocItemContainer::Impl(impl_def) => impl_def
131 .target_ty(sema.db)
132 .as_adt()
133 .map(|adt| adt.name(sema.db).to_string()),
134 }
135 });
136
137 let path_iter = module
138 .path_to_root(sema.db)
139 .into_iter()
140 .rev()
141 .filter_map(|it| it.name(sema.db))
142 .map(|name| name.to_string());
143
144 let path = if let Some(impl_trait_name) = impl_trait_name {
145 path_iter
146 .chain(std::iter::once(impl_trait_name))
147 .chain(std::iter::once(name_string))
148 .join("::")
149 } else {
150 path_iter.chain(std::iter::once(name_string)).join("::")
151 };
152
153 TestId::Path(path)
154 }
155 None => TestId::Name(name_string),
156 };
157 129
158 if test_related_attribute(&func).is_some() { 130 if test_related_attribute(&func).is_some() {
159 let attr = TestAttr::from_fn(&func); 131 let attr = TestAttr::from_fn(&func);
160 RunnableKind::Test { test_id, attr } 132 RunnableKind::Test { test_id, attr }
161 } else if func.has_atom_attr("bench") { 133 } else if func.has_atom_attr("bench") {
162 RunnableKind::Bench { test_id } 134 RunnableKind::Bench { test_id }
163 } else if has_runnable_doc_test(&attrs) {
164 RunnableKind::DocTest { test_id }
165 } else { 135 } else {
166 return None; 136 return None;
167 } 137 }
168 }; 138 };
169 139
170 let nav = if let RunnableKind::DocTest { .. } = kind { 140 let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &func));
171 NavigationTarget::from_doc_commented( 141 let cfg = def.attrs(sema.db).cfg();
172 sema.db, 142 Some(Runnable { nav, kind, cfg })
173 InFile::new(file_id.into(), &func),
174 InFile::new(file_id.into(), &func),
175 )
176 } else {
177 NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &func))
178 };
179 Some(Runnable { nav, kind, cfg: attrs.cfg() })
180} 143}
181 144
182fn runnable_struct( 145fn runnable_doctest(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> {
183 sema: &Semantics<RootDatabase>, 146 match_ast! {
184 strukt: ast::Struct, 147 match item {
185 file_id: FileId, 148 ast::Fn(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
186) -> Option<Runnable> { 149 ast::Struct(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
187 let def = sema.to_def(&strukt)?; 150 ast::Enum(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
188 let name_string = strukt.name()?.text().to_string(); 151 ast::Union(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
152 ast::Trait(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
153 ast::Const(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
154 ast::Static(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
155 ast::TypeAlias(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
156 _ => None,
157 }
158 }
159}
189 160
190 let attrs = def.attrs(sema.db); 161fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> {
162 let attrs = match def {
163 hir::ModuleDef::Module(it) => it.attrs(sema.db),
164 hir::ModuleDef::Function(it) => it.attrs(sema.db),
165 hir::ModuleDef::Adt(it) => it.attrs(sema.db),
166 hir::ModuleDef::EnumVariant(it) => it.attrs(sema.db),
167 hir::ModuleDef::Const(it) => it.attrs(sema.db),
168 hir::ModuleDef::Static(it) => it.attrs(sema.db),
169 hir::ModuleDef::Trait(it) => it.attrs(sema.db),
170 hir::ModuleDef::TypeAlias(it) => it.attrs(sema.db),
171 hir::ModuleDef::BuiltinType(_) => return None,
172 };
191 if !has_runnable_doc_test(&attrs) { 173 if !has_runnable_doc_test(&attrs) {
192 return None; 174 return None;
193 } 175 }
194 let test_id = match sema.to_def(&strukt).map(|def| def.module(sema.db)) { 176 let def_name = def.name(sema.db).map(|it| it.to_string());
195 Some(module) => { 177 let test_id = def
196 let path_iter = module 178 .canonical_path(sema.db)
197 .path_to_root(sema.db) 179 // This probably belongs to canonical path?
198 .into_iter() 180 .map(|path| {
199 .rev() 181 let assoc_def = match def {
200 .filter_map(|it| it.name(sema.db)) 182 hir::ModuleDef::Function(it) => it.as_assoc_item(sema.db),
201 .map(|name| name.to_string()); 183 hir::ModuleDef::Const(it) => it.as_assoc_item(sema.db),
202 let path = path_iter.chain(std::iter::once(name_string)).join("::"); 184 hir::ModuleDef::TypeAlias(it) => it.as_assoc_item(sema.db),
203 185 _ => None,
204 TestId::Path(path) 186 };
205 } 187 // FIXME: this also looks very wrong
206 None => TestId::Name(name_string), 188 if let Some(assoc_def) = assoc_def {
207 }; 189 if let hir::AssocItemContainer::Impl(imp) = assoc_def.container(sema.db) {
208 190 if let Some(adt) = imp.target_ty(sema.db).as_adt() {
209 let nav = NavigationTarget::from_doc_commented( 191 let name = adt.name(sema.db).to_string();
210 sema.db, 192 let idx = path.rfind(':').unwrap_or(0);
211 InFile::new(file_id.into(), &strukt), 193 let (prefix, suffix) = path.split_at(idx);
212 InFile::new(file_id.into(), &strukt), 194 return format!("{}{}::{}", prefix, name, suffix);
213 ); 195 }
214 Some(Runnable { nav, kind: RunnableKind::DocTest { test_id }, cfg: attrs.cfg() }) 196 }
197 }
198 path
199 })
200 .map(TestId::Path)
201 .or_else(|| def_name.clone().map(TestId::Name))?;
202
203 let mut nav = def.try_to_nav(sema.db)?;
204 nav.focus_range = None;
205 nav.description = None;
206 nav.docs = None;
207 nav.kind = syntax::SyntaxKind::COMMENT;
208 let res = Runnable { nav, kind: RunnableKind::DocTest { test_id }, cfg: attrs.cfg() };
209 Some(res)
215} 210}
216 211
217#[derive(Debug, Copy, Clone)] 212#[derive(Debug, Copy, Clone)]
@@ -309,7 +304,7 @@ mod tests {
309 304
310 use crate::fixture; 305 use crate::fixture;
311 306
312 use super::{RunnableAction, BENCH, BIN, DOCTEST, TEST}; 307 use super::*;
313 308
314 fn check( 309 fn check(
315 ra_fixture: &str, 310 ra_fixture: &str,
@@ -538,7 +533,7 @@ struct StructWithRunnable(String);
538 full_range: 15..74, 533 full_range: 15..74,
539 focus_range: None, 534 focus_range: None,
540 name: "should_have_runnable", 535 name: "should_have_runnable",
541 kind: FN, 536 kind: COMMENT,
542 container_name: None, 537 container_name: None,
543 description: None, 538 description: None,
544 docs: None, 539 docs: None,
@@ -558,7 +553,7 @@ struct StructWithRunnable(String);
558 full_range: 76..148, 553 full_range: 76..148,
559 focus_range: None, 554 focus_range: None,
560 name: "should_have_runnable_1", 555 name: "should_have_runnable_1",
561 kind: FN, 556 kind: COMMENT,
562 container_name: None, 557 container_name: None,
563 description: None, 558 description: None,
564 docs: None, 559 docs: None,
@@ -578,7 +573,7 @@ struct StructWithRunnable(String);
578 full_range: 150..254, 573 full_range: 150..254,
579 focus_range: None, 574 focus_range: None,
580 name: "should_have_runnable_2", 575 name: "should_have_runnable_2",
581 kind: FN, 576 kind: COMMENT,
582 container_name: None, 577 container_name: None,
583 description: None, 578 description: None,
584 docs: None, 579 docs: None,
@@ -598,7 +593,7 @@ struct StructWithRunnable(String);
598 full_range: 756..821, 593 full_range: 756..821,
599 focus_range: None, 594 focus_range: None,
600 name: "StructWithRunnable", 595 name: "StructWithRunnable",
601 kind: STRUCT, 596 kind: COMMENT,
602 container_name: None, 597 container_name: None,
603 description: None, 598 description: None,
604 docs: None, 599 docs: None,
@@ -660,7 +655,7 @@ impl Data {
660 full_range: 44..98, 655 full_range: 44..98,
661 focus_range: None, 656 focus_range: None,
662 name: "foo", 657 name: "foo",
663 kind: FN, 658 kind: COMMENT,
664 container_name: None, 659 container_name: None,
665 description: None, 660 description: None,
666 docs: None, 661 docs: None,