diff options
author | Aleksey Kladov <[email protected]> | 2020-12-17 16:29:31 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2020-12-17 17:11:40 +0000 |
commit | c888f1de6fa93d85e0fd662efd5a85421e86b326 (patch) | |
tree | 711d8244ad54a433b5102008d8a46779f0dcc55d /crates/ide | |
parent | 0da1532ef2ecda96612a5ccc326b8c6c070106d6 (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.rs | 19 | ||||
-rw-r--r-- | crates/ide/src/runnables.rs | 171 |
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 | ||
13 | use crate::{display::ToNav, FileId, NavigationTarget}; | 13 | use crate::{ |
14 | display::{ToNav, TryToNav}, | ||
15 | FileId, NavigationTarget, | ||
16 | }; | ||
14 | 17 | ||
15 | #[derive(Debug, Clone)] | 18 | #[derive(Debug, Clone)] |
16 | pub struct Runnable { | 19 | pub 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 | ||
114 | fn runnable_fn(sema: &Semantics<RootDatabase>, func: ast::Fn, file_id: FileId) -> Option<Runnable> { | 117 | fn 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 | ||
182 | fn runnable_struct( | 145 | fn 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); | 161 | fn 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, |