]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | use itertools::Itertools; |
2 | use syntax::{ | |
353b0b11 FG |
3 | ast::{self, make, AstNode, AstToken}, |
4 | match_ast, ted, NodeOrToken, SyntaxElement, TextRange, TextSize, T, | |
064997fb FG |
5 | }; |
6 | ||
7 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | |
8 | ||
9 | // Assist: remove_dbg | |
10 | // | |
11 | // Removes `dbg!()` macro call. | |
12 | // | |
13 | // ``` | |
14 | // fn main() { | |
353b0b11 | 15 | // let x = $0dbg!(42 * dbg!(4 + 2));$0 |
064997fb FG |
16 | // } |
17 | // ``` | |
18 | // -> | |
19 | // ``` | |
20 | // fn main() { | |
353b0b11 | 21 | // let x = 42 * (4 + 2); |
064997fb FG |
22 | // } |
23 | // ``` | |
24 | pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { | |
487cf647 | 25 | let macro_calls = if ctx.has_empty_selection() { |
353b0b11 | 26 | vec![ctx.find_node_at_offset::<ast::MacroExpr>()?] |
487cf647 FG |
27 | } else { |
28 | ctx.covering_element() | |
29 | .as_node()? | |
30 | .descendants() | |
31 | .filter(|node| ctx.selection_trimmed().contains_range(node.text_range())) | |
353b0b11 FG |
32 | // When the selection exactly covers the macro call to be removed, `covering_element()` |
33 | // returns `ast::MacroCall` instead of its parent `ast::MacroExpr` that we want. So | |
34 | // first try finding `ast::MacroCall`s and then retrieve their parent. | |
487cf647 | 35 | .filter_map(ast::MacroCall::cast) |
353b0b11 | 36 | .filter_map(|it| it.syntax().parent().and_then(ast::MacroExpr::cast)) |
487cf647 FG |
37 | .collect() |
38 | }; | |
39 | ||
40 | let replacements = | |
41 | macro_calls.into_iter().filter_map(compute_dbg_replacement).collect::<Vec<_>>(); | |
42 | if replacements.is_empty() { | |
43 | return None; | |
44 | } | |
45 | ||
46 | acc.add( | |
47 | AssistId("remove_dbg", AssistKind::Refactor), | |
48 | "Remove dbg!()", | |
353b0b11 | 49 | replacements.iter().map(|&(range, _)| range).reduce(|acc, range| acc.cover(range)).unwrap(), |
487cf647 | 50 | |builder| { |
353b0b11 FG |
51 | for (range, expr) in replacements { |
52 | if let Some(expr) = expr { | |
53 | builder.replace(range, expr.to_string()); | |
54 | } else { | |
55 | builder.delete(range); | |
56 | } | |
487cf647 FG |
57 | } |
58 | }, | |
59 | ) | |
60 | } | |
61 | ||
353b0b11 FG |
62 | /// Returns `None` when either |
63 | /// - macro call is not `dbg!()` | |
64 | /// - any node inside `dbg!()` could not be parsed as an expression | |
65 | /// - (`macro_expr` has no parent - is that possible?) | |
66 | /// | |
67 | /// Returns `Some(_, None)` when the macro call should just be removed. | |
68 | fn compute_dbg_replacement(macro_expr: ast::MacroExpr) -> Option<(TextRange, Option<ast::Expr>)> { | |
69 | let macro_call = macro_expr.macro_call()?; | |
064997fb FG |
70 | let tt = macro_call.token_tree()?; |
71 | let r_delim = NodeOrToken::Token(tt.right_delimiter_token()?); | |
72 | if macro_call.path()?.segment()?.name_ref()?.text() != "dbg" | |
73 | || macro_call.excl_token().is_none() | |
74 | { | |
75 | return None; | |
76 | } | |
77 | ||
78 | let mac_input = tt.syntax().children_with_tokens().skip(1).take_while(|it| *it != r_delim); | |
79 | let input_expressions = mac_input.group_by(|tok| tok.kind() == T![,]); | |
80 | let input_expressions = input_expressions | |
81 | .into_iter() | |
9c376795 | 82 | .filter_map(|(is_sep, group)| (!is_sep).then_some(group)) |
064997fb FG |
83 | .map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join(""))) |
84 | .collect::<Option<Vec<ast::Expr>>>()?; | |
85 | ||
064997fb | 86 | let parent = macro_expr.syntax().parent()?; |
487cf647 | 87 | Some(match &*input_expressions { |
064997fb FG |
88 | // dbg!() |
89 | [] => { | |
90 | match_ast! { | |
91 | match parent { | |
353b0b11 | 92 | ast::StmtList(_) => { |
064997fb FG |
93 | let range = macro_expr.syntax().text_range(); |
94 | let range = match whitespace_start(macro_expr.syntax().prev_sibling_or_token()) { | |
95 | Some(start) => range.cover_offset(start), | |
96 | None => range, | |
97 | }; | |
353b0b11 | 98 | (range, None) |
064997fb FG |
99 | }, |
100 | ast::ExprStmt(it) => { | |
101 | let range = it.syntax().text_range(); | |
102 | let range = match whitespace_start(it.syntax().prev_sibling_or_token()) { | |
103 | Some(start) => range.cover_offset(start), | |
104 | None => range, | |
105 | }; | |
353b0b11 | 106 | (range, None) |
064997fb | 107 | }, |
353b0b11 | 108 | _ => (macro_call.syntax().text_range(), Some(make::expr_unit())), |
064997fb FG |
109 | } |
110 | } | |
111 | } | |
112 | // dbg!(expr0) | |
113 | [expr] => { | |
353b0b11 | 114 | // dbg!(expr, &parent); |
064997fb FG |
115 | let wrap = match ast::Expr::cast(parent) { |
116 | Some(parent) => match (expr, parent) { | |
117 | (ast::Expr::CastExpr(_), ast::Expr::CastExpr(_)) => false, | |
118 | ( | |
353b0b11 FG |
119 | ast::Expr::BoxExpr(_) |
120 | | ast::Expr::PrefixExpr(_) | |
121 | | ast::Expr::RefExpr(_) | |
122 | | ast::Expr::MacroExpr(_), | |
064997fb FG |
123 | ast::Expr::AwaitExpr(_) |
124 | | ast::Expr::CallExpr(_) | |
125 | | ast::Expr::CastExpr(_) | |
126 | | ast::Expr::FieldExpr(_) | |
127 | | ast::Expr::IndexExpr(_) | |
128 | | ast::Expr::MethodCallExpr(_) | |
129 | | ast::Expr::RangeExpr(_) | |
130 | | ast::Expr::TryExpr(_), | |
131 | ) => true, | |
132 | ( | |
353b0b11 FG |
133 | ast::Expr::BinExpr(_) |
134 | | ast::Expr::CastExpr(_) | |
135 | | ast::Expr::RangeExpr(_) | |
136 | | ast::Expr::MacroExpr(_), | |
064997fb FG |
137 | ast::Expr::AwaitExpr(_) |
138 | | ast::Expr::BinExpr(_) | |
139 | | ast::Expr::CallExpr(_) | |
140 | | ast::Expr::CastExpr(_) | |
141 | | ast::Expr::FieldExpr(_) | |
142 | | ast::Expr::IndexExpr(_) | |
143 | | ast::Expr::MethodCallExpr(_) | |
144 | | ast::Expr::PrefixExpr(_) | |
145 | | ast::Expr::RangeExpr(_) | |
146 | | ast::Expr::RefExpr(_) | |
147 | | ast::Expr::TryExpr(_), | |
148 | ) => true, | |
149 | _ => false, | |
150 | }, | |
151 | None => false, | |
152 | }; | |
353b0b11 FG |
153 | let expr = replace_nested_dbgs(expr.clone()); |
154 | let expr = if wrap { make::expr_paren(expr) } else { expr.clone_subtree() }; | |
155 | (macro_call.syntax().text_range(), Some(expr)) | |
064997fb FG |
156 | } |
157 | // dbg!(expr0, expr1, ...) | |
353b0b11 FG |
158 | exprs => { |
159 | let exprs = exprs.iter().cloned().map(replace_nested_dbgs); | |
160 | let expr = make::expr_tuple(exprs); | |
161 | (macro_call.syntax().text_range(), Some(expr)) | |
162 | } | |
064997fb FG |
163 | }) |
164 | } | |
165 | ||
353b0b11 FG |
166 | fn replace_nested_dbgs(expanded: ast::Expr) -> ast::Expr { |
167 | if let ast::Expr::MacroExpr(mac) = &expanded { | |
168 | // Special-case when `expanded` itself is `dbg!()` since we cannot replace the whole tree | |
169 | // with `ted`. It should be fairly rare as it means the user wrote `dbg!(dbg!(..))` but you | |
170 | // never know how code ends up being! | |
171 | let replaced = if let Some((_, expr_opt)) = compute_dbg_replacement(mac.clone()) { | |
172 | match expr_opt { | |
173 | Some(expr) => expr, | |
174 | None => { | |
175 | stdx::never!("dbg! inside dbg! should not be just removed"); | |
176 | expanded | |
177 | } | |
178 | } | |
179 | } else { | |
180 | expanded | |
181 | }; | |
182 | ||
183 | return replaced; | |
184 | } | |
185 | ||
186 | let expanded = expanded.clone_for_update(); | |
187 | ||
188 | // We need to collect to avoid mutation during traversal. | |
189 | let macro_exprs: Vec<_> = | |
190 | expanded.syntax().descendants().filter_map(ast::MacroExpr::cast).collect(); | |
191 | ||
192 | for mac in macro_exprs { | |
193 | let expr_opt = match compute_dbg_replacement(mac.clone()) { | |
194 | Some((_, expr)) => expr, | |
195 | None => continue, | |
196 | }; | |
197 | ||
198 | if let Some(expr) = expr_opt { | |
199 | ted::replace(mac.syntax(), expr.syntax().clone_for_update()); | |
200 | } else { | |
201 | ted::remove(mac.syntax()); | |
202 | } | |
203 | } | |
204 | ||
205 | expanded | |
206 | } | |
207 | ||
064997fb FG |
208 | fn whitespace_start(it: Option<SyntaxElement>) -> Option<TextSize> { |
209 | Some(it?.into_token().and_then(ast::Whitespace::cast)?.syntax().text_range().start()) | |
210 | } | |
211 | ||
212 | #[cfg(test)] | |
213 | mod tests { | |
214 | use crate::tests::{check_assist, check_assist_not_applicable}; | |
215 | ||
216 | use super::*; | |
217 | ||
218 | fn check(ra_fixture_before: &str, ra_fixture_after: &str) { | |
219 | check_assist( | |
220 | remove_dbg, | |
487cf647 FG |
221 | &format!("fn main() {{\n{ra_fixture_before}\n}}"), |
222 | &format!("fn main() {{\n{ra_fixture_after}\n}}"), | |
064997fb FG |
223 | ); |
224 | } | |
225 | ||
226 | #[test] | |
227 | fn test_remove_dbg() { | |
228 | check("$0dbg!(1 + 1)", "1 + 1"); | |
229 | check("dbg!$0(1 + 1)", "1 + 1"); | |
230 | check("dbg!(1 $0+ 1)", "1 + 1"); | |
231 | check("dbg![$01 + 1]", "1 + 1"); | |
232 | check("dbg!{$01 + 1}", "1 + 1"); | |
233 | } | |
234 | ||
235 | #[test] | |
236 | fn test_remove_dbg_not_applicable() { | |
237 | check_assist_not_applicable(remove_dbg, "fn main() {$0vec![1, 2, 3]}"); | |
238 | check_assist_not_applicable(remove_dbg, "fn main() {$0dbg(5, 6, 7)}"); | |
239 | check_assist_not_applicable(remove_dbg, "fn main() {$0dbg!(5, 6, 7}"); | |
240 | } | |
241 | ||
242 | #[test] | |
243 | fn test_remove_dbg_keep_semicolon_in_let() { | |
244 | // https://github.com/rust-lang/rust-analyzer/issues/5129#issuecomment-651399779 | |
245 | check( | |
246 | r#"let res = $0dbg!(1 * 20); // needless comment"#, | |
247 | r#"let res = 1 * 20; // needless comment"#, | |
248 | ); | |
249 | check(r#"let res = $0dbg!(); // needless comment"#, r#"let res = (); // needless comment"#); | |
250 | check( | |
251 | r#"let res = $0dbg!(1, 2); // needless comment"#, | |
252 | r#"let res = (1, 2); // needless comment"#, | |
253 | ); | |
254 | } | |
255 | ||
256 | #[test] | |
257 | fn test_remove_dbg_cast_cast() { | |
258 | check(r#"let res = $0dbg!(x as u32) as u32;"#, r#"let res = x as u32 as u32;"#); | |
259 | } | |
260 | ||
261 | #[test] | |
262 | fn test_remove_dbg_prefix() { | |
263 | check(r#"let res = $0dbg!(&result).foo();"#, r#"let res = (&result).foo();"#); | |
264 | check(r#"let res = &$0dbg!(&result);"#, r#"let res = &&result;"#); | |
265 | check(r#"let res = $0dbg!(!result) && true;"#, r#"let res = !result && true;"#); | |
266 | } | |
267 | ||
268 | #[test] | |
269 | fn test_remove_dbg_post_expr() { | |
270 | check(r#"let res = $0dbg!(fut.await).foo();"#, r#"let res = fut.await.foo();"#); | |
271 | check(r#"let res = $0dbg!(result?).foo();"#, r#"let res = result?.foo();"#); | |
272 | check(r#"let res = $0dbg!(foo as u32).foo();"#, r#"let res = (foo as u32).foo();"#); | |
273 | check(r#"let res = $0dbg!(array[3]).foo();"#, r#"let res = array[3].foo();"#); | |
274 | check(r#"let res = $0dbg!(tuple.3).foo();"#, r#"let res = tuple.3.foo();"#); | |
275 | } | |
276 | ||
277 | #[test] | |
278 | fn test_remove_dbg_range_expr() { | |
279 | check(r#"let res = $0dbg!(foo..bar).foo();"#, r#"let res = (foo..bar).foo();"#); | |
280 | check(r#"let res = $0dbg!(foo..=bar).foo();"#, r#"let res = (foo..=bar).foo();"#); | |
281 | } | |
282 | ||
283 | #[test] | |
284 | fn test_remove_empty_dbg() { | |
285 | check_assist(remove_dbg, r#"fn foo() { $0dbg!(); }"#, r#"fn foo() { }"#); | |
286 | check_assist( | |
287 | remove_dbg, | |
288 | r#" | |
289 | fn foo() { | |
290 | $0dbg!(); | |
291 | } | |
292 | "#, | |
293 | r#" | |
294 | fn foo() { | |
295 | } | |
296 | "#, | |
297 | ); | |
298 | check_assist( | |
299 | remove_dbg, | |
300 | r#" | |
301 | fn foo() { | |
302 | let test = $0dbg!(); | |
303 | }"#, | |
304 | r#" | |
305 | fn foo() { | |
306 | let test = (); | |
307 | }"#, | |
308 | ); | |
309 | check_assist( | |
310 | remove_dbg, | |
311 | r#" | |
312 | fn foo() { | |
313 | let t = { | |
314 | println!("Hello, world"); | |
315 | $0dbg!() | |
316 | }; | |
317 | }"#, | |
318 | r#" | |
319 | fn foo() { | |
320 | let t = { | |
321 | println!("Hello, world"); | |
322 | }; | |
323 | }"#, | |
324 | ); | |
325 | } | |
326 | ||
327 | #[test] | |
328 | fn test_remove_multi_dbg() { | |
329 | check(r#"$0dbg!(0, 1)"#, r#"(0, 1)"#); | |
330 | check(r#"$0dbg!(0, (1, 2))"#, r#"(0, (1, 2))"#); | |
331 | } | |
487cf647 FG |
332 | |
333 | #[test] | |
334 | fn test_range() { | |
335 | check( | |
336 | r#" | |
337 | fn f() { | |
338 | dbg!(0) + $0dbg!(1); | |
339 | dbg!(())$0 | |
340 | } | |
341 | "#, | |
342 | r#" | |
343 | fn f() { | |
344 | dbg!(0) + 1; | |
345 | () | |
346 | } | |
347 | "#, | |
348 | ); | |
349 | } | |
350 | ||
351 | #[test] | |
352 | fn test_range_partial() { | |
353 | check_assist_not_applicable(remove_dbg, r#"$0dbg$0!(0)"#); | |
354 | check_assist_not_applicable(remove_dbg, r#"$0dbg!(0$0)"#); | |
355 | } | |
353b0b11 FG |
356 | |
357 | #[test] | |
358 | fn test_nested_dbg() { | |
359 | check( | |
360 | r#"$0let x = dbg!(dbg!(dbg!(dbg!(0 + 1)) * 2) + dbg!(3));$0"#, | |
361 | r#"let x = ((0 + 1) * 2) + 3;"#, | |
362 | ); | |
363 | check(r#"$0dbg!(10, dbg!(), dbg!(20, 30))$0"#, r#"(10, (), (20, 30))"#); | |
364 | } | |
365 | ||
366 | #[test] | |
367 | fn test_multiple_nested_dbg() { | |
368 | check( | |
369 | r#" | |
370 | fn f() { | |
371 | $0dbg!(); | |
372 | let x = dbg!(dbg!(dbg!(0 + 1)) + 2) + dbg!(3); | |
373 | dbg!(10, dbg!(), dbg!(20, 30));$0 | |
374 | } | |
375 | "#, | |
376 | r#" | |
377 | fn f() { | |
378 | let x = ((0 + 1) + 2) + 3; | |
379 | (10, (), (20, 30)); | |
380 | } | |
381 | "#, | |
382 | ); | |
383 | } | |
064997fb | 384 | } |