diff options
Diffstat (limited to 'crates/assists/src/utils.rs')
-rw-r--r-- | crates/assists/src/utils.rs | 250 |
1 files changed, 0 insertions, 250 deletions
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs deleted file mode 100644 index fc9f83bab..000000000 --- a/crates/assists/src/utils.rs +++ /dev/null | |||
@@ -1,250 +0,0 @@ | |||
1 | //! Assorted functions shared by several assists. | ||
2 | |||
3 | use std::ops; | ||
4 | |||
5 | use hir::HasSource; | ||
6 | use ide_db::{helpers::SnippetCap, RootDatabase}; | ||
7 | use itertools::Itertools; | ||
8 | use syntax::{ | ||
9 | ast::edit::AstNodeEdit, | ||
10 | ast::AttrsOwner, | ||
11 | ast::NameOwner, | ||
12 | ast::{self, edit, make, ArgListOwner}, | ||
13 | AstNode, Direction, | ||
14 | SyntaxKind::*, | ||
15 | SyntaxNode, TextSize, T, | ||
16 | }; | ||
17 | |||
18 | use crate::ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}; | ||
19 | |||
20 | pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { | ||
21 | extract_trivial_expression(&block) | ||
22 | .filter(|expr| !expr.syntax().text().contains_char('\n')) | ||
23 | .unwrap_or_else(|| block.into()) | ||
24 | } | ||
25 | |||
26 | pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option<ast::Expr> { | ||
27 | let has_anything_else = |thing: &SyntaxNode| -> bool { | ||
28 | let mut non_trivial_children = | ||
29 | block.syntax().children_with_tokens().filter(|it| match it.kind() { | ||
30 | WHITESPACE | T!['{'] | T!['}'] => false, | ||
31 | _ => it.as_node() != Some(thing), | ||
32 | }); | ||
33 | non_trivial_children.next().is_some() | ||
34 | }; | ||
35 | |||
36 | if let Some(expr) = block.tail_expr() { | ||
37 | if has_anything_else(expr.syntax()) { | ||
38 | return None; | ||
39 | } | ||
40 | return Some(expr); | ||
41 | } | ||
42 | // Unwrap `{ continue; }` | ||
43 | let (stmt,) = block.statements().next_tuple()?; | ||
44 | if let ast::Stmt::ExprStmt(expr_stmt) = stmt { | ||
45 | if has_anything_else(expr_stmt.syntax()) { | ||
46 | return None; | ||
47 | } | ||
48 | let expr = expr_stmt.expr()?; | ||
49 | match expr.syntax().kind() { | ||
50 | CONTINUE_EXPR | BREAK_EXPR | RETURN_EXPR => return Some(expr), | ||
51 | _ => (), | ||
52 | } | ||
53 | } | ||
54 | None | ||
55 | } | ||
56 | |||
57 | /// This is a method with a heuristics to support test methods annotated with custom test annotations, such as | ||
58 | /// `#[test_case(...)]`, `#[tokio::test]` and similar. | ||
59 | /// Also a regular `#[test]` annotation is supported. | ||
60 | /// | ||
61 | /// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test, | ||
62 | /// but it's better than not to have the runnables for the tests at all. | ||
63 | pub fn test_related_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> { | ||
64 | fn_def.attrs().find_map(|attr| { | ||
65 | let path = attr.path()?; | ||
66 | if path.syntax().text().to_string().contains("test") { | ||
67 | Some(attr) | ||
68 | } else { | ||
69 | None | ||
70 | } | ||
71 | }) | ||
72 | } | ||
73 | |||
74 | #[derive(Copy, Clone, PartialEq)] | ||
75 | pub enum DefaultMethods { | ||
76 | Only, | ||
77 | No, | ||
78 | } | ||
79 | |||
80 | pub fn filter_assoc_items( | ||
81 | db: &RootDatabase, | ||
82 | items: &[hir::AssocItem], | ||
83 | default_methods: DefaultMethods, | ||
84 | ) -> Vec<ast::AssocItem> { | ||
85 | fn has_def_name(item: &ast::AssocItem) -> bool { | ||
86 | match item { | ||
87 | ast::AssocItem::Fn(def) => def.name(), | ||
88 | ast::AssocItem::TypeAlias(def) => def.name(), | ||
89 | ast::AssocItem::Const(def) => def.name(), | ||
90 | ast::AssocItem::MacroCall(_) => None, | ||
91 | } | ||
92 | .is_some() | ||
93 | } | ||
94 | |||
95 | items | ||
96 | .iter() | ||
97 | // Note: This throws away items with no source. | ||
98 | .filter_map(|i| { | ||
99 | let item = match i { | ||
100 | hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(db)?.value), | ||
101 | hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(db)?.value), | ||
102 | hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(db)?.value), | ||
103 | }; | ||
104 | Some(item) | ||
105 | }) | ||
106 | .filter(has_def_name) | ||
107 | .filter(|it| match it { | ||
108 | ast::AssocItem::Fn(def) => matches!( | ||
109 | (default_methods, def.body()), | ||
110 | (DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None) | ||
111 | ), | ||
112 | _ => default_methods == DefaultMethods::No, | ||
113 | }) | ||
114 | .collect::<Vec<_>>() | ||
115 | } | ||
116 | |||
117 | pub fn add_trait_assoc_items_to_impl( | ||
118 | sema: &hir::Semantics<ide_db::RootDatabase>, | ||
119 | items: Vec<ast::AssocItem>, | ||
120 | trait_: hir::Trait, | ||
121 | impl_def: ast::Impl, | ||
122 | target_scope: hir::SemanticsScope, | ||
123 | ) -> (ast::Impl, ast::AssocItem) { | ||
124 | let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list); | ||
125 | |||
126 | let n_existing_items = impl_item_list.assoc_items().count(); | ||
127 | let source_scope = sema.scope_for_def(trait_); | ||
128 | let ast_transform = QualifyPaths::new(&target_scope, &source_scope) | ||
129 | .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone())); | ||
130 | |||
131 | let items = items | ||
132 | .into_iter() | ||
133 | .map(|it| ast_transform::apply(&*ast_transform, it)) | ||
134 | .map(|it| match it { | ||
135 | ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)), | ||
136 | ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()), | ||
137 | _ => it, | ||
138 | }) | ||
139 | .map(|it| edit::remove_attrs_and_docs(&it)); | ||
140 | |||
141 | let new_impl_item_list = impl_item_list.append_items(items); | ||
142 | let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list); | ||
143 | let first_new_item = | ||
144 | new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap(); | ||
145 | return (new_impl_def, first_new_item); | ||
146 | |||
147 | fn add_body(fn_def: ast::Fn) -> ast::Fn { | ||
148 | match fn_def.body() { | ||
149 | Some(_) => fn_def, | ||
150 | None => { | ||
151 | let body = | ||
152 | make::block_expr(None, Some(make::expr_todo())).indent(edit::IndentLevel(1)); | ||
153 | fn_def.with_body(body) | ||
154 | } | ||
155 | } | ||
156 | } | ||
157 | } | ||
158 | |||
159 | #[derive(Clone, Copy, Debug)] | ||
160 | pub(crate) enum Cursor<'a> { | ||
161 | Replace(&'a SyntaxNode), | ||
162 | Before(&'a SyntaxNode), | ||
163 | } | ||
164 | |||
165 | impl<'a> Cursor<'a> { | ||
166 | fn node(self) -> &'a SyntaxNode { | ||
167 | match self { | ||
168 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
169 | } | ||
170 | } | ||
171 | } | ||
172 | |||
173 | pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor) -> String { | ||
174 | assert!(cursor.node().ancestors().any(|it| it == *node)); | ||
175 | let range = cursor.node().text_range() - node.text_range().start(); | ||
176 | let range: ops::Range<usize> = range.into(); | ||
177 | |||
178 | let mut placeholder = cursor.node().to_string(); | ||
179 | escape(&mut placeholder); | ||
180 | let tab_stop = match cursor { | ||
181 | Cursor::Replace(placeholder) => format!("${{0:{}}}", placeholder), | ||
182 | Cursor::Before(placeholder) => format!("$0{}", placeholder), | ||
183 | }; | ||
184 | |||
185 | let mut buf = node.to_string(); | ||
186 | buf.replace_range(range, &tab_stop); | ||
187 | return buf; | ||
188 | |||
189 | fn escape(buf: &mut String) { | ||
190 | stdx::replace(buf, '{', r"\{"); | ||
191 | stdx::replace(buf, '}', r"\}"); | ||
192 | stdx::replace(buf, '$', r"\$"); | ||
193 | } | ||
194 | } | ||
195 | |||
196 | pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize { | ||
197 | node.children_with_tokens() | ||
198 | .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) | ||
199 | .map(|it| it.text_range().start()) | ||
200 | .unwrap_or_else(|| node.text_range().start()) | ||
201 | } | ||
202 | |||
203 | pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr { | ||
204 | if let Some(expr) = invert_special_case(&expr) { | ||
205 | return expr; | ||
206 | } | ||
207 | make::expr_prefix(T![!], expr) | ||
208 | } | ||
209 | |||
210 | fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> { | ||
211 | match expr { | ||
212 | ast::Expr::BinExpr(bin) => match bin.op_kind()? { | ||
213 | ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()), | ||
214 | ast::BinOp::EqualityTest => bin.replace_op(T![!=]).map(|it| it.into()), | ||
215 | // Parenthesize composite boolean expressions before prefixing `!` | ||
216 | ast::BinOp::BooleanAnd | ast::BinOp::BooleanOr => { | ||
217 | Some(make::expr_prefix(T![!], make::expr_paren(expr.clone()))) | ||
218 | } | ||
219 | _ => None, | ||
220 | }, | ||
221 | ast::Expr::MethodCallExpr(mce) => { | ||
222 | let receiver = mce.receiver()?; | ||
223 | let method = mce.name_ref()?; | ||
224 | let arg_list = mce.arg_list()?; | ||
225 | |||
226 | let method = match method.text().as_str() { | ||
227 | "is_some" => "is_none", | ||
228 | "is_none" => "is_some", | ||
229 | "is_ok" => "is_err", | ||
230 | "is_err" => "is_ok", | ||
231 | _ => return None, | ||
232 | }; | ||
233 | Some(make::expr_method_call(receiver, method, arg_list)) | ||
234 | } | ||
235 | ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::PrefixOp::Not => { | ||
236 | if let ast::Expr::ParenExpr(parexpr) = pe.expr()? { | ||
237 | parexpr.expr() | ||
238 | } else { | ||
239 | pe.expr() | ||
240 | } | ||
241 | } | ||
242 | // FIXME: | ||
243 | // ast::Expr::Literal(true | false ) | ||
244 | _ => None, | ||
245 | } | ||
246 | } | ||
247 | |||
248 | pub(crate) fn next_prev() -> impl Iterator<Item = Direction> { | ||
249 | [Direction::Next, Direction::Prev].iter().copied() | ||
250 | } | ||