diff options
Diffstat (limited to 'crates/ide_assists/src/handlers/remove_dbg.rs')
-rw-r--r-- | crates/ide_assists/src/handlers/remove_dbg.rs | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/crates/ide_assists/src/handlers/remove_dbg.rs b/crates/ide_assists/src/handlers/remove_dbg.rs new file mode 100644 index 000000000..6114091f2 --- /dev/null +++ b/crates/ide_assists/src/handlers/remove_dbg.rs | |||
@@ -0,0 +1,421 @@ | |||
1 | use syntax::{ | ||
2 | ast::{self, AstNode}, | ||
3 | match_ast, SyntaxElement, TextRange, TextSize, T, | ||
4 | }; | ||
5 | |||
6 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | ||
7 | |||
8 | // Assist: remove_dbg | ||
9 | // | ||
10 | // Removes `dbg!()` macro call. | ||
11 | // | ||
12 | // ``` | ||
13 | // fn main() { | ||
14 | // $0dbg!(92); | ||
15 | // } | ||
16 | // ``` | ||
17 | // -> | ||
18 | // ``` | ||
19 | // fn main() { | ||
20 | // 92; | ||
21 | // } | ||
22 | // ``` | ||
23 | pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
24 | let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; | ||
25 | let new_contents = adjusted_macro_contents(¯o_call)?; | ||
26 | |||
27 | let macro_text_range = macro_call.syntax().text_range(); | ||
28 | let macro_end = if macro_call.semicolon_token().is_some() { | ||
29 | macro_text_range.end() - TextSize::of(';') | ||
30 | } else { | ||
31 | macro_text_range.end() | ||
32 | }; | ||
33 | |||
34 | acc.add( | ||
35 | AssistId("remove_dbg", AssistKind::Refactor), | ||
36 | "Remove dbg!()", | ||
37 | macro_text_range, | ||
38 | |builder| { | ||
39 | builder.replace(TextRange::new(macro_text_range.start(), macro_end), new_contents); | ||
40 | }, | ||
41 | ) | ||
42 | } | ||
43 | |||
44 | fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> { | ||
45 | let contents = get_valid_macrocall_contents(¯o_call, "dbg")?; | ||
46 | let macro_text_with_brackets = macro_call.token_tree()?.syntax().text(); | ||
47 | let macro_text_in_brackets = macro_text_with_brackets.slice(TextRange::new( | ||
48 | TextSize::of('('), | ||
49 | macro_text_with_brackets.len() - TextSize::of(')'), | ||
50 | )); | ||
51 | |||
52 | Some( | ||
53 | if !is_leaf_or_control_flow_expr(macro_call) | ||
54 | && needs_parentheses_around_macro_contents(contents) | ||
55 | { | ||
56 | format!("({})", macro_text_in_brackets) | ||
57 | } else { | ||
58 | macro_text_in_brackets.to_string() | ||
59 | }, | ||
60 | ) | ||
61 | } | ||
62 | |||
63 | fn is_leaf_or_control_flow_expr(macro_call: &ast::MacroCall) -> bool { | ||
64 | macro_call.syntax().next_sibling().is_none() | ||
65 | || match macro_call.syntax().parent() { | ||
66 | Some(parent) => match_ast! { | ||
67 | match parent { | ||
68 | ast::Condition(_it) => true, | ||
69 | ast::MatchExpr(_it) => true, | ||
70 | _ => false, | ||
71 | } | ||
72 | }, | ||
73 | None => false, | ||
74 | } | ||
75 | } | ||
76 | |||
77 | /// Verifies that the given macro_call actually matches the given name | ||
78 | /// and contains proper ending tokens, then returns the contents between the ending tokens | ||
79 | fn get_valid_macrocall_contents( | ||
80 | macro_call: &ast::MacroCall, | ||
81 | macro_name: &str, | ||
82 | ) -> Option<Vec<SyntaxElement>> { | ||
83 | let path = macro_call.path()?; | ||
84 | let name_ref = path.segment()?.name_ref()?; | ||
85 | |||
86 | // Make sure it is actually a dbg-macro call, dbg followed by ! | ||
87 | let excl = path.syntax().next_sibling_or_token()?; | ||
88 | if name_ref.text() != macro_name || excl.kind() != T![!] { | ||
89 | return None; | ||
90 | } | ||
91 | |||
92 | let mut children_with_tokens = macro_call.token_tree()?.syntax().children_with_tokens(); | ||
93 | let first_child = children_with_tokens.next()?; | ||
94 | let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>(); | ||
95 | let last_child = contents_between_brackets.pop()?; | ||
96 | |||
97 | if contents_between_brackets.is_empty() { | ||
98 | None | ||
99 | } else { | ||
100 | match (first_child.kind(), last_child.kind()) { | ||
101 | (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => { | ||
102 | Some(contents_between_brackets) | ||
103 | } | ||
104 | _ => None, | ||
105 | } | ||
106 | } | ||
107 | } | ||
108 | |||
109 | fn needs_parentheses_around_macro_contents(macro_contents: Vec<SyntaxElement>) -> bool { | ||
110 | if macro_contents.len() < 2 { | ||
111 | return false; | ||
112 | } | ||
113 | let mut macro_contents = macro_contents.into_iter().peekable(); | ||
114 | let mut unpaired_brackets_in_contents = Vec::new(); | ||
115 | while let Some(element) = macro_contents.next() { | ||
116 | match element.kind() { | ||
117 | T!['('] | T!['['] | T!['{'] => unpaired_brackets_in_contents.push(element), | ||
118 | T![')'] => { | ||
119 | if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['(']) | ||
120 | { | ||
121 | return true; | ||
122 | } | ||
123 | } | ||
124 | T![']'] => { | ||
125 | if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['[']) | ||
126 | { | ||
127 | return true; | ||
128 | } | ||
129 | } | ||
130 | T!['}'] => { | ||
131 | if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['{']) | ||
132 | { | ||
133 | return true; | ||
134 | } | ||
135 | } | ||
136 | symbol_kind => { | ||
137 | let symbol_not_in_bracket = unpaired_brackets_in_contents.is_empty(); | ||
138 | if symbol_not_in_bracket | ||
139 | && symbol_kind != T![:] // paths | ||
140 | && (symbol_kind != T![.] // field/method access | ||
141 | || macro_contents // range expressions consist of two SyntaxKind::Dot in macro invocations | ||
142 | .peek() | ||
143 | .map(|element| element.kind() == T![.]) | ||
144 | .unwrap_or(false)) | ||
145 | && symbol_kind != T![?] // try operator | ||
146 | && (symbol_kind.is_punct() || symbol_kind == T![as]) | ||
147 | { | ||
148 | return true; | ||
149 | } | ||
150 | } | ||
151 | } | ||
152 | } | ||
153 | !unpaired_brackets_in_contents.is_empty() | ||
154 | } | ||
155 | |||
156 | #[cfg(test)] | ||
157 | mod tests { | ||
158 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
159 | |||
160 | use super::*; | ||
161 | |||
162 | #[test] | ||
163 | fn test_remove_dbg() { | ||
164 | check_assist(remove_dbg, "$0dbg!(1 + 1)", "1 + 1"); | ||
165 | |||
166 | check_assist(remove_dbg, "dbg!$0((1 + 1))", "(1 + 1)"); | ||
167 | |||
168 | check_assist(remove_dbg, "dbg!(1 $0+ 1)", "1 + 1"); | ||
169 | |||
170 | check_assist(remove_dbg, "let _ = $0dbg!(1 + 1)", "let _ = 1 + 1"); | ||
171 | |||
172 | check_assist( | ||
173 | remove_dbg, | ||
174 | " | ||
175 | fn foo(n: usize) { | ||
176 | if let Some(_) = dbg!(n.$0checked_sub(4)) { | ||
177 | // ... | ||
178 | } | ||
179 | } | ||
180 | ", | ||
181 | " | ||
182 | fn foo(n: usize) { | ||
183 | if let Some(_) = n.checked_sub(4) { | ||
184 | // ... | ||
185 | } | ||
186 | } | ||
187 | ", | ||
188 | ); | ||
189 | |||
190 | check_assist(remove_dbg, "$0dbg!(Foo::foo_test()).bar()", "Foo::foo_test().bar()"); | ||
191 | } | ||
192 | |||
193 | #[test] | ||
194 | fn test_remove_dbg_with_brackets_and_braces() { | ||
195 | check_assist(remove_dbg, "dbg![$01 + 1]", "1 + 1"); | ||
196 | check_assist(remove_dbg, "dbg!{$01 + 1}", "1 + 1"); | ||
197 | } | ||
198 | |||
199 | #[test] | ||
200 | fn test_remove_dbg_not_applicable() { | ||
201 | check_assist_not_applicable(remove_dbg, "$0vec![1, 2, 3]"); | ||
202 | check_assist_not_applicable(remove_dbg, "$0dbg(5, 6, 7)"); | ||
203 | check_assist_not_applicable(remove_dbg, "$0dbg!(5, 6, 7"); | ||
204 | } | ||
205 | |||
206 | #[test] | ||
207 | fn test_remove_dbg_target() { | ||
208 | check_assist_target( | ||
209 | remove_dbg, | ||
210 | " | ||
211 | fn foo(n: usize) { | ||
212 | if let Some(_) = dbg!(n.$0checked_sub(4)) { | ||
213 | // ... | ||
214 | } | ||
215 | } | ||
216 | ", | ||
217 | "dbg!(n.checked_sub(4))", | ||
218 | ); | ||
219 | } | ||
220 | |||
221 | #[test] | ||
222 | fn test_remove_dbg_keep_semicolon() { | ||
223 | // https://github.com/rust-analyzer/rust-analyzer/issues/5129#issuecomment-651399779 | ||
224 | // not quite though | ||
225 | // adding a comment at the end of the line makes | ||
226 | // the ast::MacroCall to include the semicolon at the end | ||
227 | check_assist( | ||
228 | remove_dbg, | ||
229 | r#"let res = $0dbg!(1 * 20); // needless comment"#, | ||
230 | r#"let res = 1 * 20; // needless comment"#, | ||
231 | ); | ||
232 | } | ||
233 | |||
234 | #[test] | ||
235 | fn remove_dbg_from_non_leaf_simple_expression() { | ||
236 | check_assist( | ||
237 | remove_dbg, | ||
238 | " | ||
239 | fn main() { | ||
240 | let mut a = 1; | ||
241 | while dbg!$0(a) < 10000 { | ||
242 | a += 1; | ||
243 | } | ||
244 | } | ||
245 | ", | ||
246 | " | ||
247 | fn main() { | ||
248 | let mut a = 1; | ||
249 | while a < 10000 { | ||
250 | a += 1; | ||
251 | } | ||
252 | } | ||
253 | ", | ||
254 | ); | ||
255 | } | ||
256 | |||
257 | #[test] | ||
258 | fn test_remove_dbg_keep_expression() { | ||
259 | check_assist( | ||
260 | remove_dbg, | ||
261 | r#"let res = $0dbg!(a + b).foo();"#, | ||
262 | r#"let res = (a + b).foo();"#, | ||
263 | ); | ||
264 | |||
265 | check_assist(remove_dbg, r#"let res = $0dbg!(2 + 2) * 5"#, r#"let res = (2 + 2) * 5"#); | ||
266 | check_assist(remove_dbg, r#"let res = $0dbg![2 + 2] * 5"#, r#"let res = (2 + 2) * 5"#); | ||
267 | } | ||
268 | |||
269 | #[test] | ||
270 | fn test_remove_dbg_method_chaining() { | ||
271 | check_assist( | ||
272 | remove_dbg, | ||
273 | r#"let res = $0dbg!(foo().bar()).baz();"#, | ||
274 | r#"let res = foo().bar().baz();"#, | ||
275 | ); | ||
276 | check_assist( | ||
277 | remove_dbg, | ||
278 | r#"let res = $0dbg!(foo.bar()).baz();"#, | ||
279 | r#"let res = foo.bar().baz();"#, | ||
280 | ); | ||
281 | } | ||
282 | |||
283 | #[test] | ||
284 | fn test_remove_dbg_field_chaining() { | ||
285 | check_assist(remove_dbg, r#"let res = $0dbg!(foo.bar).baz;"#, r#"let res = foo.bar.baz;"#); | ||
286 | } | ||
287 | |||
288 | #[test] | ||
289 | fn test_remove_dbg_from_inside_fn() { | ||
290 | check_assist_target( | ||
291 | remove_dbg, | ||
292 | r#" | ||
293 | fn square(x: u32) -> u32 { | ||
294 | x * x | ||
295 | } | ||
296 | |||
297 | fn main() { | ||
298 | let x = square(dbg$0!(5 + 10)); | ||
299 | println!("{}", x); | ||
300 | }"#, | ||
301 | "dbg!(5 + 10)", | ||
302 | ); | ||
303 | |||
304 | check_assist( | ||
305 | remove_dbg, | ||
306 | r#" | ||
307 | fn square(x: u32) -> u32 { | ||
308 | x * x | ||
309 | } | ||
310 | |||
311 | fn main() { | ||
312 | let x = square(dbg$0!(5 + 10)); | ||
313 | println!("{}", x); | ||
314 | }"#, | ||
315 | r#" | ||
316 | fn square(x: u32) -> u32 { | ||
317 | x * x | ||
318 | } | ||
319 | |||
320 | fn main() { | ||
321 | let x = square(5 + 10); | ||
322 | println!("{}", x); | ||
323 | }"#, | ||
324 | ); | ||
325 | } | ||
326 | |||
327 | #[test] | ||
328 | fn test_remove_dbg_try_expr() { | ||
329 | check_assist( | ||
330 | remove_dbg, | ||
331 | r#"let res = $0dbg!(result?).foo();"#, | ||
332 | r#"let res = result?.foo();"#, | ||
333 | ); | ||
334 | } | ||
335 | |||
336 | #[test] | ||
337 | fn test_remove_dbg_await_expr() { | ||
338 | check_assist( | ||
339 | remove_dbg, | ||
340 | r#"let res = $0dbg!(fut.await).foo();"#, | ||
341 | r#"let res = fut.await.foo();"#, | ||
342 | ); | ||
343 | } | ||
344 | |||
345 | #[test] | ||
346 | fn test_remove_dbg_as_cast() { | ||
347 | check_assist( | ||
348 | remove_dbg, | ||
349 | r#"let res = $0dbg!(3 as usize).foo();"#, | ||
350 | r#"let res = (3 as usize).foo();"#, | ||
351 | ); | ||
352 | } | ||
353 | |||
354 | #[test] | ||
355 | fn test_remove_dbg_index_expr() { | ||
356 | check_assist( | ||
357 | remove_dbg, | ||
358 | r#"let res = $0dbg!(array[3]).foo();"#, | ||
359 | r#"let res = array[3].foo();"#, | ||
360 | ); | ||
361 | check_assist( | ||
362 | remove_dbg, | ||
363 | r#"let res = $0dbg!(tuple.3).foo();"#, | ||
364 | r#"let res = tuple.3.foo();"#, | ||
365 | ); | ||
366 | } | ||
367 | |||
368 | #[test] | ||
369 | fn test_remove_dbg_range_expr() { | ||
370 | check_assist( | ||
371 | remove_dbg, | ||
372 | r#"let res = $0dbg!(foo..bar).foo();"#, | ||
373 | r#"let res = (foo..bar).foo();"#, | ||
374 | ); | ||
375 | check_assist( | ||
376 | remove_dbg, | ||
377 | r#"let res = $0dbg!(foo..=bar).foo();"#, | ||
378 | r#"let res = (foo..=bar).foo();"#, | ||
379 | ); | ||
380 | } | ||
381 | |||
382 | #[test] | ||
383 | fn test_remove_dbg_followed_by_block() { | ||
384 | check_assist( | ||
385 | remove_dbg, | ||
386 | r#"fn foo() { | ||
387 | if $0dbg!(x || y) {} | ||
388 | }"#, | ||
389 | r#"fn foo() { | ||
390 | if x || y {} | ||
391 | }"#, | ||
392 | ); | ||
393 | check_assist( | ||
394 | remove_dbg, | ||
395 | r#"fn foo() { | ||
396 | while let foo = $0dbg!(&x) {} | ||
397 | }"#, | ||
398 | r#"fn foo() { | ||
399 | while let foo = &x {} | ||
400 | }"#, | ||
401 | ); | ||
402 | check_assist( | ||
403 | remove_dbg, | ||
404 | r#"fn foo() { | ||
405 | if let foo = $0dbg!(&x) {} | ||
406 | }"#, | ||
407 | r#"fn foo() { | ||
408 | if let foo = &x {} | ||
409 | }"#, | ||
410 | ); | ||
411 | check_assist( | ||
412 | remove_dbg, | ||
413 | r#"fn foo() { | ||
414 | match $0dbg!(&x) {} | ||
415 | }"#, | ||
416 | r#"fn foo() { | ||
417 | match &x {} | ||
418 | }"#, | ||
419 | ); | ||
420 | } | ||
421 | } | ||