diff options
author | Chetan Khilosiya <[email protected]> | 2021-02-22 18:47:48 +0000 |
---|---|---|
committer | Chetan Khilosiya <[email protected]> | 2021-02-22 19:29:16 +0000 |
commit | e4756cb4f6e66097638b9d101589358976be2ba8 (patch) | |
tree | b6ca0ae6b45b57834476ae0f9985cec3a6bd9090 /crates/assists/src/utils.rs | |
parent | 8687053b118f47ce1a4962d0baa19b22d40d2758 (diff) |
7526: Rename crate assists to ide_assists.
Diffstat (limited to 'crates/assists/src/utils.rs')
-rw-r--r-- | crates/assists/src/utils.rs | 434 |
1 files changed, 0 insertions, 434 deletions
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs deleted file mode 100644 index 0074da741..000000000 --- a/crates/assists/src/utils.rs +++ /dev/null | |||
@@ -1,434 +0,0 @@ | |||
1 | //! Assorted functions shared by several assists. | ||
2 | |||
3 | use std::ops; | ||
4 | |||
5 | use ast::TypeBoundsOwner; | ||
6 | use hir::{Adt, HasSource}; | ||
7 | use ide_db::{helpers::SnippetCap, RootDatabase}; | ||
8 | use itertools::Itertools; | ||
9 | use stdx::format_to; | ||
10 | use syntax::{ | ||
11 | ast::edit::AstNodeEdit, | ||
12 | ast::AttrsOwner, | ||
13 | ast::NameOwner, | ||
14 | ast::{self, edit, make, ArgListOwner, GenericParamsOwner}, | ||
15 | AstNode, Direction, SmolStr, | ||
16 | SyntaxKind::*, | ||
17 | SyntaxNode, TextSize, T, | ||
18 | }; | ||
19 | |||
20 | use crate::{ | ||
21 | assist_context::AssistContext, | ||
22 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | ||
23 | }; | ||
24 | |||
25 | pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { | ||
26 | extract_trivial_expression(&block) | ||
27 | .filter(|expr| !expr.syntax().text().contains_char('\n')) | ||
28 | .unwrap_or_else(|| block.into()) | ||
29 | } | ||
30 | |||
31 | pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option<ast::Expr> { | ||
32 | let has_anything_else = |thing: &SyntaxNode| -> bool { | ||
33 | let mut non_trivial_children = | ||
34 | block.syntax().children_with_tokens().filter(|it| match it.kind() { | ||
35 | WHITESPACE | T!['{'] | T!['}'] => false, | ||
36 | _ => it.as_node() != Some(thing), | ||
37 | }); | ||
38 | non_trivial_children.next().is_some() | ||
39 | }; | ||
40 | |||
41 | if let Some(expr) = block.tail_expr() { | ||
42 | if has_anything_else(expr.syntax()) { | ||
43 | return None; | ||
44 | } | ||
45 | return Some(expr); | ||
46 | } | ||
47 | // Unwrap `{ continue; }` | ||
48 | let (stmt,) = block.statements().next_tuple()?; | ||
49 | if let ast::Stmt::ExprStmt(expr_stmt) = stmt { | ||
50 | if has_anything_else(expr_stmt.syntax()) { | ||
51 | return None; | ||
52 | } | ||
53 | let expr = expr_stmt.expr()?; | ||
54 | match expr.syntax().kind() { | ||
55 | CONTINUE_EXPR | BREAK_EXPR | RETURN_EXPR => return Some(expr), | ||
56 | _ => (), | ||
57 | } | ||
58 | } | ||
59 | None | ||
60 | } | ||
61 | |||
62 | /// This is a method with a heuristics to support test methods annotated with custom test annotations, such as | ||
63 | /// `#[test_case(...)]`, `#[tokio::test]` and similar. | ||
64 | /// Also a regular `#[test]` annotation is supported. | ||
65 | /// | ||
66 | /// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test, | ||
67 | /// but it's better than not to have the runnables for the tests at all. | ||
68 | pub fn test_related_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> { | ||
69 | fn_def.attrs().find_map(|attr| { | ||
70 | let path = attr.path()?; | ||
71 | if path.syntax().text().to_string().contains("test") { | ||
72 | Some(attr) | ||
73 | } else { | ||
74 | None | ||
75 | } | ||
76 | }) | ||
77 | } | ||
78 | |||
79 | #[derive(Copy, Clone, PartialEq)] | ||
80 | pub enum DefaultMethods { | ||
81 | Only, | ||
82 | No, | ||
83 | } | ||
84 | |||
85 | pub fn filter_assoc_items( | ||
86 | db: &RootDatabase, | ||
87 | items: &[hir::AssocItem], | ||
88 | default_methods: DefaultMethods, | ||
89 | ) -> Vec<ast::AssocItem> { | ||
90 | fn has_def_name(item: &ast::AssocItem) -> bool { | ||
91 | match item { | ||
92 | ast::AssocItem::Fn(def) => def.name(), | ||
93 | ast::AssocItem::TypeAlias(def) => def.name(), | ||
94 | ast::AssocItem::Const(def) => def.name(), | ||
95 | ast::AssocItem::MacroCall(_) => None, | ||
96 | } | ||
97 | .is_some() | ||
98 | } | ||
99 | |||
100 | items | ||
101 | .iter() | ||
102 | // Note: This throws away items with no source. | ||
103 | .filter_map(|i| { | ||
104 | let item = match i { | ||
105 | hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(db)?.value), | ||
106 | hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(db)?.value), | ||
107 | hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(db)?.value), | ||
108 | }; | ||
109 | Some(item) | ||
110 | }) | ||
111 | .filter(has_def_name) | ||
112 | .filter(|it| match it { | ||
113 | ast::AssocItem::Fn(def) => matches!( | ||
114 | (default_methods, def.body()), | ||
115 | (DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None) | ||
116 | ), | ||
117 | _ => default_methods == DefaultMethods::No, | ||
118 | }) | ||
119 | .collect::<Vec<_>>() | ||
120 | } | ||
121 | |||
122 | pub fn add_trait_assoc_items_to_impl( | ||
123 | sema: &hir::Semantics<ide_db::RootDatabase>, | ||
124 | items: Vec<ast::AssocItem>, | ||
125 | trait_: hir::Trait, | ||
126 | impl_def: ast::Impl, | ||
127 | target_scope: hir::SemanticsScope, | ||
128 | ) -> (ast::Impl, ast::AssocItem) { | ||
129 | let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list); | ||
130 | |||
131 | let n_existing_items = impl_item_list.assoc_items().count(); | ||
132 | let source_scope = sema.scope_for_def(trait_); | ||
133 | let ast_transform = QualifyPaths::new(&target_scope, &source_scope) | ||
134 | .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone())); | ||
135 | |||
136 | let items = items | ||
137 | .into_iter() | ||
138 | .map(|it| ast_transform::apply(&*ast_transform, it)) | ||
139 | .map(|it| match it { | ||
140 | ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)), | ||
141 | ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()), | ||
142 | _ => it, | ||
143 | }) | ||
144 | .map(|it| edit::remove_attrs_and_docs(&it)); | ||
145 | |||
146 | let new_impl_item_list = impl_item_list.append_items(items); | ||
147 | let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list); | ||
148 | let first_new_item = | ||
149 | new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap(); | ||
150 | return (new_impl_def, first_new_item); | ||
151 | |||
152 | fn add_body(fn_def: ast::Fn) -> ast::Fn { | ||
153 | match fn_def.body() { | ||
154 | Some(_) => fn_def, | ||
155 | None => { | ||
156 | let body = | ||
157 | make::block_expr(None, Some(make::expr_todo())).indent(edit::IndentLevel(1)); | ||
158 | fn_def.with_body(body) | ||
159 | } | ||
160 | } | ||
161 | } | ||
162 | } | ||
163 | |||
164 | #[derive(Clone, Copy, Debug)] | ||
165 | pub(crate) enum Cursor<'a> { | ||
166 | Replace(&'a SyntaxNode), | ||
167 | Before(&'a SyntaxNode), | ||
168 | } | ||
169 | |||
170 | impl<'a> Cursor<'a> { | ||
171 | fn node(self) -> &'a SyntaxNode { | ||
172 | match self { | ||
173 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
174 | } | ||
175 | } | ||
176 | } | ||
177 | |||
178 | pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor) -> String { | ||
179 | assert!(cursor.node().ancestors().any(|it| it == *node)); | ||
180 | let range = cursor.node().text_range() - node.text_range().start(); | ||
181 | let range: ops::Range<usize> = range.into(); | ||
182 | |||
183 | let mut placeholder = cursor.node().to_string(); | ||
184 | escape(&mut placeholder); | ||
185 | let tab_stop = match cursor { | ||
186 | Cursor::Replace(placeholder) => format!("${{0:{}}}", placeholder), | ||
187 | Cursor::Before(placeholder) => format!("$0{}", placeholder), | ||
188 | }; | ||
189 | |||
190 | let mut buf = node.to_string(); | ||
191 | buf.replace_range(range, &tab_stop); | ||
192 | return buf; | ||
193 | |||
194 | fn escape(buf: &mut String) { | ||
195 | stdx::replace(buf, '{', r"\{"); | ||
196 | stdx::replace(buf, '}', r"\}"); | ||
197 | stdx::replace(buf, '$', r"\$"); | ||
198 | } | ||
199 | } | ||
200 | |||
201 | pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize { | ||
202 | node.children_with_tokens() | ||
203 | .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) | ||
204 | .map(|it| it.text_range().start()) | ||
205 | .unwrap_or_else(|| node.text_range().start()) | ||
206 | } | ||
207 | |||
208 | pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr { | ||
209 | if let Some(expr) = invert_special_case(&expr) { | ||
210 | return expr; | ||
211 | } | ||
212 | make::expr_prefix(T![!], expr) | ||
213 | } | ||
214 | |||
215 | fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> { | ||
216 | match expr { | ||
217 | ast::Expr::BinExpr(bin) => match bin.op_kind()? { | ||
218 | ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()), | ||
219 | ast::BinOp::EqualityTest => bin.replace_op(T![!=]).map(|it| it.into()), | ||
220 | // Parenthesize composite boolean expressions before prefixing `!` | ||
221 | ast::BinOp::BooleanAnd | ast::BinOp::BooleanOr => { | ||
222 | Some(make::expr_prefix(T![!], make::expr_paren(expr.clone()))) | ||
223 | } | ||
224 | _ => None, | ||
225 | }, | ||
226 | ast::Expr::MethodCallExpr(mce) => { | ||
227 | let receiver = mce.receiver()?; | ||
228 | let method = mce.name_ref()?; | ||
229 | let arg_list = mce.arg_list()?; | ||
230 | |||
231 | let method = match method.text() { | ||
232 | "is_some" => "is_none", | ||
233 | "is_none" => "is_some", | ||
234 | "is_ok" => "is_err", | ||
235 | "is_err" => "is_ok", | ||
236 | _ => return None, | ||
237 | }; | ||
238 | Some(make::expr_method_call(receiver, method, arg_list)) | ||
239 | } | ||
240 | ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::PrefixOp::Not => { | ||
241 | if let ast::Expr::ParenExpr(parexpr) = pe.expr()? { | ||
242 | parexpr.expr() | ||
243 | } else { | ||
244 | pe.expr() | ||
245 | } | ||
246 | } | ||
247 | // FIXME: | ||
248 | // ast::Expr::Literal(true | false ) | ||
249 | _ => None, | ||
250 | } | ||
251 | } | ||
252 | |||
253 | pub(crate) fn next_prev() -> impl Iterator<Item = Direction> { | ||
254 | [Direction::Next, Direction::Prev].iter().copied() | ||
255 | } | ||
256 | |||
257 | pub(crate) fn does_pat_match_variant(pat: &ast::Pat, var: &ast::Pat) -> bool { | ||
258 | let first_node_text = |pat: &ast::Pat| pat.syntax().first_child().map(|node| node.text()); | ||
259 | |||
260 | let pat_head = match pat { | ||
261 | ast::Pat::IdentPat(bind_pat) => { | ||
262 | if let Some(p) = bind_pat.pat() { | ||
263 | first_node_text(&p) | ||
264 | } else { | ||
265 | return pat.syntax().text() == var.syntax().text(); | ||
266 | } | ||
267 | } | ||
268 | pat => first_node_text(pat), | ||
269 | }; | ||
270 | |||
271 | let var_head = first_node_text(var); | ||
272 | |||
273 | pat_head == var_head | ||
274 | } | ||
275 | |||
276 | // Uses a syntax-driven approach to find any impl blocks for the struct that | ||
277 | // exist within the module/file | ||
278 | // | ||
279 | // Returns `None` if we've found an existing fn | ||
280 | // | ||
281 | // FIXME: change the new fn checking to a more semantic approach when that's more | ||
282 | // viable (e.g. we process proc macros, etc) | ||
283 | // FIXME: this partially overlaps with `find_impl_block_*` | ||
284 | pub(crate) fn find_struct_impl( | ||
285 | ctx: &AssistContext, | ||
286 | strukt: &ast::Adt, | ||
287 | name: &str, | ||
288 | ) -> Option<Option<ast::Impl>> { | ||
289 | let db = ctx.db(); | ||
290 | let module = strukt.syntax().ancestors().find(|node| { | ||
291 | ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) | ||
292 | })?; | ||
293 | |||
294 | let struct_def = match strukt { | ||
295 | ast::Adt::Enum(e) => Adt::Enum(ctx.sema.to_def(e)?), | ||
296 | ast::Adt::Struct(s) => Adt::Struct(ctx.sema.to_def(s)?), | ||
297 | ast::Adt::Union(u) => Adt::Union(ctx.sema.to_def(u)?), | ||
298 | }; | ||
299 | |||
300 | let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| { | ||
301 | let blk = ctx.sema.to_def(&impl_blk)?; | ||
302 | |||
303 | // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}` | ||
304 | // (we currently use the wrong type parameter) | ||
305 | // also we wouldn't want to use e.g. `impl S<u32>` | ||
306 | |||
307 | let same_ty = match blk.target_ty(db).as_adt() { | ||
308 | Some(def) => def == struct_def, | ||
309 | None => false, | ||
310 | }; | ||
311 | let not_trait_impl = blk.target_trait(db).is_none(); | ||
312 | |||
313 | if !(same_ty && not_trait_impl) { | ||
314 | None | ||
315 | } else { | ||
316 | Some(impl_blk) | ||
317 | } | ||
318 | }); | ||
319 | |||
320 | if let Some(ref impl_blk) = block { | ||
321 | if has_fn(impl_blk, name) { | ||
322 | return None; | ||
323 | } | ||
324 | } | ||
325 | |||
326 | Some(block) | ||
327 | } | ||
328 | |||
329 | fn has_fn(imp: &ast::Impl, rhs_name: &str) -> bool { | ||
330 | if let Some(il) = imp.assoc_item_list() { | ||
331 | for item in il.assoc_items() { | ||
332 | if let ast::AssocItem::Fn(f) = item { | ||
333 | if let Some(name) = f.name() { | ||
334 | if name.text().eq_ignore_ascii_case(rhs_name) { | ||
335 | return true; | ||
336 | } | ||
337 | } | ||
338 | } | ||
339 | } | ||
340 | } | ||
341 | |||
342 | false | ||
343 | } | ||
344 | |||
345 | /// Find the start of the `impl` block for the given `ast::Impl`. | ||
346 | // | ||
347 | // FIXME: this partially overlaps with `find_struct_impl` | ||
348 | pub(crate) fn find_impl_block_start(impl_def: ast::Impl, buf: &mut String) -> Option<TextSize> { | ||
349 | buf.push('\n'); | ||
350 | let start = impl_def.assoc_item_list().and_then(|it| it.l_curly_token())?.text_range().end(); | ||
351 | Some(start) | ||
352 | } | ||
353 | |||
354 | /// Find the end of the `impl` block for the given `ast::Impl`. | ||
355 | // | ||
356 | // FIXME: this partially overlaps with `find_struct_impl` | ||
357 | pub(crate) fn find_impl_block_end(impl_def: ast::Impl, buf: &mut String) -> Option<TextSize> { | ||
358 | buf.push('\n'); | ||
359 | let end = impl_def | ||
360 | .assoc_item_list() | ||
361 | .and_then(|it| it.r_curly_token())? | ||
362 | .prev_sibling_or_token()? | ||
363 | .text_range() | ||
364 | .end(); | ||
365 | Some(end) | ||
366 | } | ||
367 | |||
368 | // Generates the surrounding `impl Type { <code> }` including type and lifetime | ||
369 | // parameters | ||
370 | pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String { | ||
371 | generate_impl_text_inner(adt, None, code) | ||
372 | } | ||
373 | |||
374 | // Generates the surrounding `impl <trait> for Type { <code> }` including type | ||
375 | // and lifetime parameters | ||
376 | pub(crate) fn generate_trait_impl_text(adt: &ast::Adt, trait_text: &str, code: &str) -> String { | ||
377 | generate_impl_text_inner(adt, Some(trait_text), code) | ||
378 | } | ||
379 | |||
380 | fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str) -> String { | ||
381 | let generic_params = adt.generic_param_list(); | ||
382 | let mut buf = String::with_capacity(code.len()); | ||
383 | buf.push_str("\n\n"); | ||
384 | adt.attrs() | ||
385 | .filter(|attr| attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false)) | ||
386 | .for_each(|attr| buf.push_str(format!("{}\n", attr.to_string()).as_str())); | ||
387 | buf.push_str("impl"); | ||
388 | if let Some(generic_params) = &generic_params { | ||
389 | let lifetimes = generic_params.lifetime_params().map(|lt| format!("{}", lt.syntax())); | ||
390 | let type_params = generic_params.type_params().map(|type_param| { | ||
391 | let mut buf = String::new(); | ||
392 | if let Some(it) = type_param.name() { | ||
393 | format_to!(buf, "{}", it.syntax()); | ||
394 | } | ||
395 | if let Some(it) = type_param.colon_token() { | ||
396 | format_to!(buf, "{} ", it); | ||
397 | } | ||
398 | if let Some(it) = type_param.type_bound_list() { | ||
399 | format_to!(buf, "{}", it.syntax()); | ||
400 | } | ||
401 | buf | ||
402 | }); | ||
403 | let generics = lifetimes.chain(type_params).format(", "); | ||
404 | format_to!(buf, "<{}>", generics); | ||
405 | } | ||
406 | buf.push(' '); | ||
407 | if let Some(trait_text) = trait_text { | ||
408 | buf.push_str(trait_text); | ||
409 | buf.push_str(" for "); | ||
410 | } | ||
411 | buf.push_str(adt.name().unwrap().text()); | ||
412 | if let Some(generic_params) = generic_params { | ||
413 | let lifetime_params = generic_params | ||
414 | .lifetime_params() | ||
415 | .filter_map(|it| it.lifetime()) | ||
416 | .map(|it| SmolStr::from(it.text())); | ||
417 | let type_params = generic_params | ||
418 | .type_params() | ||
419 | .filter_map(|it| it.name()) | ||
420 | .map(|it| SmolStr::from(it.text())); | ||
421 | format_to!(buf, "<{}>", lifetime_params.chain(type_params).format(", ")) | ||
422 | } | ||
423 | |||
424 | match adt.where_clause() { | ||
425 | Some(where_clause) => { | ||
426 | format_to!(buf, "\n{}\n{{\n{}\n}}", where_clause, code); | ||
427 | } | ||
428 | None => { | ||
429 | format_to!(buf, " {{\n{}\n}}", code); | ||
430 | } | ||
431 | } | ||
432 | |||
433 | buf | ||
434 | } | ||