diff options
Diffstat (limited to 'crates/assists/src/utils.rs')
-rw-r--r-- | crates/assists/src/utils.rs | 125 |
1 files changed, 119 insertions, 6 deletions
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs index 56f925ee6..66c0cdd5f 100644 --- a/crates/assists/src/utils.rs +++ b/crates/assists/src/utils.rs | |||
@@ -4,20 +4,25 @@ pub(crate) mod import_assets; | |||
4 | 4 | ||
5 | use std::ops; | 5 | use std::ops; |
6 | 6 | ||
7 | use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait}; | 7 | use hir::{Crate, Enum, HasSource, Module, ScopeDef, Semantics, Trait}; |
8 | use ide_db::RootDatabase; | 8 | use ide_db::RootDatabase; |
9 | use itertools::Itertools; | 9 | use itertools::Itertools; |
10 | use syntax::{ | 10 | use syntax::{ |
11 | ast::{self, make, ArgListOwner}, | 11 | ast::edit::AstNodeEdit, |
12 | ast::AttrsOwner, | ||
13 | ast::NameOwner, | ||
14 | ast::{self, edit, make, ArgListOwner}, | ||
12 | AstNode, Direction, | 15 | AstNode, Direction, |
13 | SyntaxKind::*, | 16 | SyntaxKind::*, |
14 | SyntaxNode, TextSize, T, | 17 | SyntaxNode, TextSize, T, |
15 | }; | 18 | }; |
16 | 19 | ||
17 | use crate::assist_config::SnippetCap; | 20 | use crate::{ |
21 | assist_config::SnippetCap, | ||
22 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | ||
23 | }; | ||
18 | 24 | ||
19 | pub use insert_use::MergeBehaviour; | 25 | pub use insert_use::{insert_use, ImportScope, MergeBehaviour}; |
20 | pub(crate) use insert_use::{insert_use, ImportScope}; | ||
21 | 26 | ||
22 | pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path { | 27 | pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path { |
23 | let mut segments = Vec::new(); | 28 | let mut segments = Vec::new(); |
@@ -77,6 +82,104 @@ pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option<ast::Expr> { | |||
77 | None | 82 | None |
78 | } | 83 | } |
79 | 84 | ||
85 | /// This is a method with a heuristics to support test methods annotated with custom test annotations, such as | ||
86 | /// `#[test_case(...)]`, `#[tokio::test]` and similar. | ||
87 | /// Also a regular `#[test]` annotation is supported. | ||
88 | /// | ||
89 | /// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test, | ||
90 | /// but it's better than not to have the runnables for the tests at all. | ||
91 | pub fn test_related_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> { | ||
92 | fn_def.attrs().find_map(|attr| { | ||
93 | let path = attr.path()?; | ||
94 | if path.syntax().text().to_string().contains("test") { | ||
95 | Some(attr) | ||
96 | } else { | ||
97 | None | ||
98 | } | ||
99 | }) | ||
100 | } | ||
101 | |||
102 | #[derive(Copy, Clone, PartialEq)] | ||
103 | pub enum DefaultMethods { | ||
104 | Only, | ||
105 | No, | ||
106 | } | ||
107 | |||
108 | pub fn filter_assoc_items( | ||
109 | db: &RootDatabase, | ||
110 | items: &[hir::AssocItem], | ||
111 | default_methods: DefaultMethods, | ||
112 | ) -> Vec<ast::AssocItem> { | ||
113 | fn has_def_name(item: &ast::AssocItem) -> bool { | ||
114 | match item { | ||
115 | ast::AssocItem::Fn(def) => def.name(), | ||
116 | ast::AssocItem::TypeAlias(def) => def.name(), | ||
117 | ast::AssocItem::Const(def) => def.name(), | ||
118 | ast::AssocItem::MacroCall(_) => None, | ||
119 | } | ||
120 | .is_some() | ||
121 | }; | ||
122 | |||
123 | items | ||
124 | .iter() | ||
125 | .map(|i| match i { | ||
126 | hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(db).value), | ||
127 | hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(db).value), | ||
128 | hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(db).value), | ||
129 | }) | ||
130 | .filter(has_def_name) | ||
131 | .filter(|it| match it { | ||
132 | ast::AssocItem::Fn(def) => matches!( | ||
133 | (default_methods, def.body()), | ||
134 | (DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None) | ||
135 | ), | ||
136 | _ => default_methods == DefaultMethods::No, | ||
137 | }) | ||
138 | .collect::<Vec<_>>() | ||
139 | } | ||
140 | |||
141 | pub fn add_trait_assoc_items_to_impl( | ||
142 | sema: &hir::Semantics<ide_db::RootDatabase>, | ||
143 | items: Vec<ast::AssocItem>, | ||
144 | trait_: hir::Trait, | ||
145 | impl_def: ast::Impl, | ||
146 | target_scope: hir::SemanticsScope, | ||
147 | ) -> (ast::Impl, ast::AssocItem) { | ||
148 | let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list); | ||
149 | |||
150 | let n_existing_items = impl_item_list.assoc_items().count(); | ||
151 | let source_scope = sema.scope_for_def(trait_); | ||
152 | let ast_transform = QualifyPaths::new(&target_scope, &source_scope) | ||
153 | .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone())); | ||
154 | |||
155 | let items = items | ||
156 | .into_iter() | ||
157 | .map(|it| ast_transform::apply(&*ast_transform, it)) | ||
158 | .map(|it| match it { | ||
159 | ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)), | ||
160 | ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()), | ||
161 | _ => it, | ||
162 | }) | ||
163 | .map(|it| edit::remove_attrs_and_docs(&it)); | ||
164 | |||
165 | let new_impl_item_list = impl_item_list.append_items(items); | ||
166 | let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list); | ||
167 | let first_new_item = | ||
168 | new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap(); | ||
169 | return (new_impl_def, first_new_item); | ||
170 | |||
171 | fn add_body(fn_def: ast::Fn) -> ast::Fn { | ||
172 | match fn_def.body() { | ||
173 | Some(_) => fn_def, | ||
174 | None => { | ||
175 | let body = | ||
176 | make::block_expr(None, Some(make::expr_todo())).indent(edit::IndentLevel(1)); | ||
177 | fn_def.with_body(body) | ||
178 | } | ||
179 | } | ||
180 | } | ||
181 | } | ||
182 | |||
80 | #[derive(Clone, Copy, Debug)] | 183 | #[derive(Clone, Copy, Debug)] |
81 | pub(crate) enum Cursor<'a> { | 184 | pub(crate) enum Cursor<'a> { |
82 | Replace(&'a SyntaxNode), | 185 | Replace(&'a SyntaxNode), |
@@ -171,6 +274,12 @@ pub mod convert { | |||
171 | } | 274 | } |
172 | } | 275 | } |
173 | 276 | ||
277 | pub mod default { | ||
278 | pub trait Default { | ||
279 | fn default() -> Self; | ||
280 | } | ||
281 | } | ||
282 | |||
174 | pub mod iter { | 283 | pub mod iter { |
175 | pub use self::traits::{collect::IntoIterator, iterator::Iterator}; | 284 | pub use self::traits::{collect::IntoIterator, iterator::Iterator}; |
176 | mod traits { | 285 | mod traits { |
@@ -241,7 +350,7 @@ pub mod option { | |||
241 | } | 350 | } |
242 | 351 | ||
243 | pub mod prelude { | 352 | pub mod prelude { |
244 | pub use crate::{convert::From, iter::{IntoIterator, Iterator}, option::Option::{self, *}}; | 353 | pub use crate::{convert::From, iter::{IntoIterator, Iterator}, option::Option::{self, *}, default::Default}; |
245 | } | 354 | } |
246 | #[prelude_import] | 355 | #[prelude_import] |
247 | pub use prelude::*; | 356 | pub use prelude::*; |
@@ -259,6 +368,10 @@ pub use prelude::*; | |||
259 | self.find_enum("core:option:Option") | 368 | self.find_enum("core:option:Option") |
260 | } | 369 | } |
261 | 370 | ||
371 | pub fn core_default_Default(&self) -> Option<Trait> { | ||
372 | self.find_trait("core:default:Default") | ||
373 | } | ||
374 | |||
262 | pub fn core_iter_Iterator(&self) -> Option<Trait> { | 375 | pub fn core_iter_Iterator(&self) -> Option<Trait> { |
263 | self.find_trait("core:iter:traits:iterator:Iterator") | 376 | self.find_trait("core:iter:traits:iterator:Iterator") |
264 | } | 377 | } |