diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-12-17 17:12:09 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-12-17 17:12:09 +0000 |
commit | ce3db6c9270411d34635cc6106115110c84a27f6 (patch) | |
tree | 711d8244ad54a433b5102008d8a46779f0dcc55d | |
parent | 4368a3bcebca131dd5c7ccecc54e64b3c5bd0c78 (diff) | |
parent | c888f1de6fa93d85e0fd662efd5a85421e86b326 (diff) |
Merge #6919
6919: Rewrite doctest runnables r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r-- | crates/ide/src/display/navigation_target.rs | 19 | ||||
-rw-r--r-- | crates/ide/src/runnables.rs | 191 |
2 files changed, 89 insertions, 121 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 a2a0ad43d..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,125 +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( | 117 | fn runnable_fn(sema: &Semantics<RootDatabase>, func: ast::Fn, file_id: FileId) -> Option<Runnable> { |
115 | sema: &Semantics<RootDatabase>, | 118 | let def = sema.to_def(&func)?; |
116 | fn_def: ast::Fn, | 119 | let name_string = func.name()?.text().to_string(); |
117 | file_id: FileId, | ||
118 | ) -> Option<Runnable> { | ||
119 | let def = sema.to_def(&fn_def)?; | ||
120 | let name_string = fn_def.name()?.text().to_string(); | ||
121 | 120 | ||
122 | let attrs = def.attrs(sema.db); | ||
123 | let kind = if name_string == "main" { | 121 | let kind = if name_string == "main" { |
124 | RunnableKind::Bin | 122 | RunnableKind::Bin |
125 | } else { | 123 | } else { |
126 | let test_id = match sema.to_def(&fn_def).map(|def| def.module(sema.db)) { | 124 | let canonical_path = sema.to_def(&func).and_then(|def| { |
127 | Some(module) => { | 125 | let def: hir::ModuleDef = def.into(); |
128 | let def = sema.to_def(&fn_def)?; | 126 | def.canonical_path(sema.db) |
129 | let impl_trait_name = def.as_assoc_item(sema.db).and_then(|assoc_item| { | 127 | }); |
130 | match assoc_item.container(sema.db) { | 128 | let test_id = canonical_path.map(TestId::Path).unwrap_or(TestId::Name(name_string)); |
131 | hir::AssocItemContainer::Trait(trait_item) => { | 129 | |
132 | Some(trait_item.name(sema.db).to_string()) | 130 | if test_related_attribute(&func).is_some() { |
133 | } | 131 | let attr = TestAttr::from_fn(&func); |
134 | hir::AssocItemContainer::Impl(impl_def) => impl_def | ||
135 | .target_ty(sema.db) | ||
136 | .as_adt() | ||
137 | .map(|adt| adt.name(sema.db).to_string()), | ||
138 | } | ||
139 | }); | ||
140 | |||
141 | let path_iter = module | ||
142 | .path_to_root(sema.db) | ||
143 | .into_iter() | ||
144 | .rev() | ||
145 | .filter_map(|it| it.name(sema.db)) | ||
146 | .map(|name| name.to_string()); | ||
147 | |||
148 | let path = if let Some(impl_trait_name) = impl_trait_name { | ||
149 | path_iter | ||
150 | .chain(std::iter::once(impl_trait_name)) | ||
151 | .chain(std::iter::once(name_string)) | ||
152 | .join("::") | ||
153 | } else { | ||
154 | path_iter.chain(std::iter::once(name_string)).join("::") | ||
155 | }; | ||
156 | |||
157 | TestId::Path(path) | ||
158 | } | ||
159 | None => TestId::Name(name_string), | ||
160 | }; | ||
161 | |||
162 | if test_related_attribute(&fn_def).is_some() { | ||
163 | let attr = TestAttr::from_fn(&fn_def); | ||
164 | RunnableKind::Test { test_id, attr } | 132 | RunnableKind::Test { test_id, attr } |
165 | } else if fn_def.has_atom_attr("bench") { | 133 | } else if func.has_atom_attr("bench") { |
166 | RunnableKind::Bench { test_id } | 134 | RunnableKind::Bench { test_id } |
167 | } else if has_runnable_doc_test(&attrs) { | ||
168 | RunnableKind::DocTest { test_id } | ||
169 | } else { | 135 | } else { |
170 | return None; | 136 | return None; |
171 | } | 137 | } |
172 | }; | 138 | }; |
173 | 139 | ||
174 | let cfg = attrs.cfg(); | 140 | let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &func)); |
175 | 141 | let cfg = def.attrs(sema.db).cfg(); | |
176 | let nav = if let RunnableKind::DocTest { .. } = kind { | ||
177 | NavigationTarget::from_doc_commented( | ||
178 | sema.db, | ||
179 | InFile::new(file_id.into(), &fn_def), | ||
180 | InFile::new(file_id.into(), &fn_def), | ||
181 | ) | ||
182 | } else { | ||
183 | NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def)) | ||
184 | }; | ||
185 | Some(Runnable { nav, kind, cfg }) | 142 | Some(Runnable { nav, kind, cfg }) |
186 | } | 143 | } |
187 | 144 | ||
188 | fn runnable_struct( | 145 | fn runnable_doctest(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { |
189 | sema: &Semantics<RootDatabase>, | 146 | match_ast! { |
190 | struct_def: ast::Struct, | 147 | match item { |
191 | file_id: FileId, | 148 | ast::Fn(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), |
192 | ) -> Option<Runnable> { | 149 | ast::Struct(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), |
193 | let def = sema.to_def(&struct_def)?; | 150 | ast::Enum(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), |
194 | let name_string = struct_def.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 | } | ||
195 | 160 | ||
196 | 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 | }; | ||
197 | if !has_runnable_doc_test(&attrs) { | 173 | if !has_runnable_doc_test(&attrs) { |
198 | return None; | 174 | return None; |
199 | } | 175 | } |
200 | let cfg = attrs.cfg(); | 176 | let def_name = def.name(sema.db).map(|it| it.to_string()); |
201 | 177 | let test_id = def | |
202 | let test_id = match sema.to_def(&struct_def).map(|def| def.module(sema.db)) { | 178 | .canonical_path(sema.db) |
203 | Some(module) => { | 179 | // This probably belongs to canonical path? |
204 | let path_iter = module | 180 | .map(|path| { |
205 | .path_to_root(sema.db) | 181 | let assoc_def = match def { |
206 | .into_iter() | 182 | hir::ModuleDef::Function(it) => it.as_assoc_item(sema.db), |
207 | .rev() | 183 | hir::ModuleDef::Const(it) => it.as_assoc_item(sema.db), |
208 | .filter_map(|it| it.name(sema.db)) | 184 | hir::ModuleDef::TypeAlias(it) => it.as_assoc_item(sema.db), |
209 | .map(|name| name.to_string()); | 185 | _ => None, |
210 | let path = path_iter.chain(std::iter::once(name_string)).join("::"); | 186 | }; |
211 | 187 | // FIXME: this also looks very wrong | |
212 | TestId::Path(path) | 188 | if let Some(assoc_def) = assoc_def { |
213 | } | 189 | if let hir::AssocItemContainer::Impl(imp) = assoc_def.container(sema.db) { |
214 | None => TestId::Name(name_string), | 190 | if let Some(adt) = imp.target_ty(sema.db).as_adt() { |
215 | }; | 191 | let name = adt.name(sema.db).to_string(); |
216 | 192 | let idx = path.rfind(':').unwrap_or(0); | |
217 | let nav = NavigationTarget::from_doc_commented( | 193 | let (prefix, suffix) = path.split_at(idx); |
218 | sema.db, | 194 | return format!("{}{}::{}", prefix, name, suffix); |
219 | InFile::new(file_id.into(), &struct_def), | 195 | } |
220 | InFile::new(file_id.into(), &struct_def), | 196 | } |
221 | ); | 197 | } |
222 | Some(Runnable { nav, kind: RunnableKind::DocTest { test_id }, cfg }) | 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) | ||
223 | } | 210 | } |
224 | 211 | ||
225 | #[derive(Debug, Copy, Clone)] | 212 | #[derive(Debug, Copy, Clone)] |
@@ -317,7 +304,7 @@ mod tests { | |||
317 | 304 | ||
318 | use crate::fixture; | 305 | use crate::fixture; |
319 | 306 | ||
320 | use super::{RunnableAction, BENCH, BIN, DOCTEST, TEST}; | 307 | use super::*; |
321 | 308 | ||
322 | fn check( | 309 | fn check( |
323 | ra_fixture: &str, | 310 | ra_fixture: &str, |
@@ -546,7 +533,7 @@ struct StructWithRunnable(String); | |||
546 | full_range: 15..74, | 533 | full_range: 15..74, |
547 | focus_range: None, | 534 | focus_range: None, |
548 | name: "should_have_runnable", | 535 | name: "should_have_runnable", |
549 | kind: FN, | 536 | kind: COMMENT, |
550 | container_name: None, | 537 | container_name: None, |
551 | description: None, | 538 | description: None, |
552 | docs: None, | 539 | docs: None, |
@@ -566,7 +553,7 @@ struct StructWithRunnable(String); | |||
566 | full_range: 76..148, | 553 | full_range: 76..148, |
567 | focus_range: None, | 554 | focus_range: None, |
568 | name: "should_have_runnable_1", | 555 | name: "should_have_runnable_1", |
569 | kind: FN, | 556 | kind: COMMENT, |
570 | container_name: None, | 557 | container_name: None, |
571 | description: None, | 558 | description: None, |
572 | docs: None, | 559 | docs: None, |
@@ -586,7 +573,7 @@ struct StructWithRunnable(String); | |||
586 | full_range: 150..254, | 573 | full_range: 150..254, |
587 | focus_range: None, | 574 | focus_range: None, |
588 | name: "should_have_runnable_2", | 575 | name: "should_have_runnable_2", |
589 | kind: FN, | 576 | kind: COMMENT, |
590 | container_name: None, | 577 | container_name: None, |
591 | description: None, | 578 | description: None, |
592 | docs: None, | 579 | docs: None, |
@@ -606,7 +593,7 @@ struct StructWithRunnable(String); | |||
606 | full_range: 756..821, | 593 | full_range: 756..821, |
607 | focus_range: None, | 594 | focus_range: None, |
608 | name: "StructWithRunnable", | 595 | name: "StructWithRunnable", |
609 | kind: STRUCT, | 596 | kind: COMMENT, |
610 | container_name: None, | 597 | container_name: None, |
611 | description: None, | 598 | description: None, |
612 | docs: None, | 599 | docs: None, |
@@ -668,7 +655,7 @@ impl Data { | |||
668 | full_range: 44..98, | 655 | full_range: 44..98, |
669 | focus_range: None, | 656 | focus_range: None, |
670 | name: "foo", | 657 | name: "foo", |
671 | kind: FN, | 658 | kind: COMMENT, |
672 | container_name: None, | 659 | container_name: None, |
673 | description: None, | 660 | description: None, |
674 | docs: None, | 661 | docs: None, |