1use itertools::Itertools;
2use syntax::{
3 Edition, NodeOrToken, SyntaxNode, SyntaxToken, T,
4 ast::{self, AstNode, make},
5 match_ast,
6 syntax_editor::{Position, SyntaxEditor},
7};
8
9use crate::{AssistContext, AssistId, Assists};
10
11pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
27 let macro_calls = if ctx.has_empty_selection() {
28 vec![ctx.find_node_at_offset::<ast::MacroExpr>()?]
29 } else {
30 ctx.covering_element()
31 .as_node()?
32 .descendants()
33 .filter(|node| ctx.selection_trimmed().contains_range(node.text_range()))
34 .filter_map(ast::MacroCall::cast)
38 .filter_map(|it| it.syntax().parent().and_then(ast::MacroExpr::cast))
39 .collect()
40 };
41
42 let replacements =
43 macro_calls.into_iter().filter_map(compute_dbg_replacement).collect::<Vec<_>>();
44 let target = replacements
45 .iter()
46 .flat_map(|(node_or_token, _)| node_or_token.iter())
47 .map(|t| t.text_range())
48 .reduce(|acc, range| acc.cover(range))?;
49 acc.add(AssistId::quick_fix("remove_dbg"), "Remove dbg!()", target, |builder| {
50 let mut editor = builder.make_editor(ctx.source_file().syntax());
51 for (range, expr) in replacements {
52 if let Some(expr) = expr {
53 editor.insert(Position::before(range[0].clone()), expr.syntax().clone_for_update());
54 }
55 for node_or_token in range {
56 editor.delete(node_or_token);
57 }
58 }
59 builder.add_file_edits(ctx.vfs_file_id(), editor);
60 })
61}
62
63fn compute_dbg_replacement(
70 macro_expr: ast::MacroExpr,
71) -> Option<(Vec<NodeOrToken<SyntaxNode, SyntaxToken>>, Option<ast::Expr>)> {
72 let macro_call = macro_expr.macro_call()?;
73 let tt = macro_call.token_tree()?;
74 let r_delim = NodeOrToken::Token(tt.right_delimiter_token()?);
75 if macro_call.path()?.segment()?.name_ref()?.text() != "dbg"
76 || macro_call.excl_token().is_none()
77 {
78 return None;
79 }
80
81 let mac_input = tt.syntax().children_with_tokens().skip(1).take_while(|it| *it != r_delim);
82 let input_expressions = mac_input.chunk_by(|tok| tok.kind() == T![,]);
83 let input_expressions = input_expressions
84 .into_iter()
85 .filter_map(|(is_sep, group)| (!is_sep).then_some(group))
86 .map(|tokens| tokens.collect::<Vec<_>>())
87 .filter(|tokens| !tokens.iter().all(|it| it.kind().is_trivia()))
88 .map(|tokens| syntax::hacks::parse_expr_from_str(&tokens.iter().join(""), Edition::CURRENT))
89 .collect::<Option<Vec<ast::Expr>>>()?;
90
91 let parent = macro_expr.syntax().parent()?;
92 Some(match &*input_expressions {
93 [] => {
95 match_ast! {
96 match parent {
97 ast::StmtList(_) => {
98 let mut replace = vec![macro_expr.syntax().clone().into()];
99 if let Some(prev_sibling) = macro_expr.syntax().prev_sibling_or_token()
100 && prev_sibling.kind() == syntax::SyntaxKind::WHITESPACE {
101 replace.push(prev_sibling);
102 }
103 (replace, None)
104 },
105 ast::ExprStmt(it) => {
106 let mut replace = vec![it.syntax().clone().into()];
107 if let Some(prev_sibling) = it.syntax().prev_sibling_or_token()
108 && prev_sibling.kind() == syntax::SyntaxKind::WHITESPACE {
109 replace.push(prev_sibling);
110 }
111 (replace, None)
112 },
113 _ => (vec![macro_call.syntax().clone().into()], Some(make::ext::expr_unit())),
114 }
115 }
116 }
117 exprs if ast::ExprStmt::can_cast(parent.kind()) && exprs.iter().all(pure_expr) => {
119 let mut replace = vec![parent.clone().into()];
120 if let Some(prev_sibling) = parent.prev_sibling_or_token()
121 && prev_sibling.kind() == syntax::SyntaxKind::WHITESPACE
122 {
123 replace.push(prev_sibling);
124 }
125 (replace, None)
126 }
127 [expr] => {
129 let wrap = match ast::Expr::cast(parent) {
131 Some(parent) => match (expr, parent) {
132 (ast::Expr::CastExpr(_), ast::Expr::CastExpr(_)) => false,
133 (
134 ast::Expr::PrefixExpr(_) | ast::Expr::RefExpr(_) | ast::Expr::MacroExpr(_),
135 ast::Expr::AwaitExpr(_)
136 | ast::Expr::CallExpr(_)
137 | ast::Expr::CastExpr(_)
138 | ast::Expr::FieldExpr(_)
139 | ast::Expr::IndexExpr(_)
140 | ast::Expr::MethodCallExpr(_)
141 | ast::Expr::RangeExpr(_)
142 | ast::Expr::TryExpr(_),
143 ) => true,
144 (
145 ast::Expr::BinExpr(_)
146 | ast::Expr::CastExpr(_)
147 | ast::Expr::RangeExpr(_)
148 | ast::Expr::MacroExpr(_),
149 ast::Expr::AwaitExpr(_)
150 | ast::Expr::BinExpr(_)
151 | ast::Expr::CallExpr(_)
152 | ast::Expr::CastExpr(_)
153 | ast::Expr::FieldExpr(_)
154 | ast::Expr::IndexExpr(_)
155 | ast::Expr::MethodCallExpr(_)
156 | ast::Expr::PrefixExpr(_)
157 | ast::Expr::RangeExpr(_)
158 | ast::Expr::RefExpr(_)
159 | ast::Expr::TryExpr(_),
160 ) => true,
161 _ => false,
162 },
163 None => false,
164 };
165 let expr = replace_nested_dbgs(expr.clone());
166 let expr = if wrap { make::expr_paren(expr).into() } else { expr.clone_subtree() };
167 (vec![macro_call.syntax().clone().into()], Some(expr))
168 }
169 exprs => {
171 let exprs = exprs.iter().cloned().map(replace_nested_dbgs);
172 let expr = make::expr_tuple(exprs);
173 (vec![macro_call.syntax().clone().into()], Some(expr.into()))
174 }
175 })
176}
177
178fn pure_expr(expr: &ast::Expr) -> bool {
179 match_ast! {
180 match (expr.syntax()) {
181 ast::Literal(_) => true,
182 ast::RefExpr(it) => {
183 matches!(it.expr(), Some(ast::Expr::PathExpr(p))
184 if p.path().and_then(|p| p.as_single_name_ref()).is_some())
185 },
186 ast::PathExpr(it) => it.path().and_then(|it| it.as_single_name_ref()).is_some(),
187 _ => false,
188 }
189 }
190}
191
192fn replace_nested_dbgs(expanded: ast::Expr) -> ast::Expr {
193 if let ast::Expr::MacroExpr(mac) = &expanded {
194 let replaced = if let Some((_, expr_opt)) = compute_dbg_replacement(mac.clone()) {
198 match expr_opt {
199 Some(expr) => expr,
200 None => {
201 stdx::never!("dbg! inside dbg! should not be just removed");
202 expanded
203 }
204 }
205 } else {
206 expanded
207 };
208
209 return replaced;
210 }
211
212 let expanded = expanded.clone_subtree();
213 let mut editor = SyntaxEditor::new(expanded.syntax().clone());
214 let macro_exprs: Vec<_> =
216 expanded.syntax().descendants().filter_map(ast::MacroExpr::cast).collect();
217
218 for mac in macro_exprs {
219 let expr_opt = match compute_dbg_replacement(mac.clone()) {
220 Some((_, expr)) => expr,
221 None => continue,
222 };
223
224 if let Some(expr) = expr_opt {
225 editor.replace(mac.syntax(), expr.syntax().clone_for_update());
226 } else {
227 editor.delete(mac.syntax());
228 }
229 }
230 let expanded_syntax = editor.finish().new_root().clone();
231 ast::Expr::cast(expanded_syntax).unwrap()
232}
233
234#[cfg(test)]
235mod tests {
236 use crate::tests::{check_assist, check_assist_not_applicable};
237
238 use super::*;
239
240 fn check(
241 #[rust_analyzer::rust_fixture] ra_fixture_before: &str,
242 #[rust_analyzer::rust_fixture] ra_fixture_after: &str,
243 ) {
244 check_assist(
245 remove_dbg,
246 &format!("fn main() {{\n{ra_fixture_before}\n}}"),
247 &format!("fn main() {{\n{ra_fixture_after}\n}}"),
248 );
249 }
250
251 #[test]
252 fn test_remove_dbg() {
253 check("$0dbg!(1 + 1)", "1 + 1");
254 check("dbg!$0(1 + 1)", "1 + 1");
255 check("dbg!(1 $0+ 1)", "1 + 1");
256 check("dbg![$01 + 1]", "1 + 1");
257 check("dbg!{$01 + 1}", "1 + 1");
258 }
259
260 #[test]
261 fn test_remove_simple_dbg_statement() {
262 check_assist(
263 remove_dbg,
264 r#"
265fn foo() {
266 let n = 2;
267 $0dbg!(3);
268 dbg!(2.6);
269 dbg!(1, 2.5);
270 dbg!('x');
271 dbg!(&n);
272 dbg!(n);
273 dbg!(n,);
274 dbg!(n, );
275 // needless comment
276 dbg!("foo");$0
277}
278"#,
279 r#"
280fn foo() {
281 let n = 2;
282 // needless comment
283}
284"#,
285 );
286 }
287
288 #[test]
289 fn test_remove_trailing_comma_dbg() {
290 check("$0dbg!(1 + 1,)", "1 + 1");
291 check("$0dbg!(1 + 1, )", "1 + 1");
292 check("$0dbg!(1 + 1,\n)", "1 + 1");
293 check("$0dbg!(1 + 1, 2 + 3)", "(1 + 1, 2 + 3)");
294 check("$0dbg!(1 + 1, 2 + 3 )", "(1 + 1, 2 + 3)");
295 check("$0dbg!(1 + 1, 2 + 3, )", "(1 + 1, 2 + 3)");
296 check("$0dbg!(1 + 1, 2 + 3 ,)", "(1 + 1, 2 + 3)");
297 }
298
299 #[test]
300 fn test_remove_dbg_not_applicable() {
301 check_assist_not_applicable(remove_dbg, "fn main() {$0vec![1, 2, 3]}");
302 check_assist_not_applicable(remove_dbg, "fn main() {$0dbg(5, 6, 7)}");
303 check_assist_not_applicable(remove_dbg, "fn main() {$0dbg!(5, 6, 7}");
304 }
305
306 #[test]
307 fn test_remove_dbg_keep_semicolon_in_let() {
308 check(
310 r#"let res = $0dbg!(1 * 20); // needless comment"#,
311 r#"let res = 1 * 20; // needless comment"#,
312 );
313 check(r#"let res = $0dbg!(); // needless comment"#, r#"let res = (); // needless comment"#);
314 check(
315 r#"let res = $0dbg!(1, 2); // needless comment"#,
316 r#"let res = (1, 2); // needless comment"#,
317 );
318 }
319
320 #[test]
321 fn test_remove_dbg_cast_cast() {
322 check(r#"let res = $0dbg!(x as u32) as u32;"#, r#"let res = x as u32 as u32;"#);
323 }
324
325 #[test]
326 fn test_remove_dbg_prefix() {
327 check(r#"let res = $0dbg!(&result).foo();"#, r#"let res = (&result).foo();"#);
328 check(r#"let res = &$0dbg!(&result);"#, r#"let res = &&result;"#);
329 check(r#"let res = $0dbg!(!result) && true;"#, r#"let res = !result && true;"#);
330 }
331
332 #[test]
333 fn test_remove_dbg_post_expr() {
334 check(r#"let res = $0dbg!(fut.await).foo();"#, r#"let res = fut.await.foo();"#);
335 check(r#"let res = $0dbg!(result?).foo();"#, r#"let res = result?.foo();"#);
336 check(r#"let res = $0dbg!(foo as u32).foo();"#, r#"let res = (foo as u32).foo();"#);
337 check(r#"let res = $0dbg!(array[3]).foo();"#, r#"let res = array[3].foo();"#);
338 check(r#"let res = $0dbg!(tuple.3).foo();"#, r#"let res = tuple.3.foo();"#);
339 }
340
341 #[test]
342 fn test_remove_dbg_range_expr() {
343 check(r#"let res = $0dbg!(foo..bar).foo();"#, r#"let res = (foo..bar).foo();"#);
344 check(r#"let res = $0dbg!(foo..=bar).foo();"#, r#"let res = (foo..=bar).foo();"#);
345 }
346
347 #[test]
348 fn test_remove_empty_dbg() {
349 check_assist(remove_dbg, r#"fn foo() { $0dbg!(); }"#, r#"fn foo() { }"#);
350 check_assist(
351 remove_dbg,
352 r#"
353fn foo() {
354 $0dbg!();
355}
356"#,
357 r#"
358fn foo() {
359}
360"#,
361 );
362 check_assist(
363 remove_dbg,
364 r#"
365fn foo() {
366 let test = $0dbg!();
367}"#,
368 r#"
369fn foo() {
370 let test = ();
371}"#,
372 );
373 check_assist(
374 remove_dbg,
375 r#"
376fn foo() {
377 let t = {
378 println!("Hello, world");
379 $0dbg!()
380 };
381}"#,
382 r#"
383fn foo() {
384 let t = {
385 println!("Hello, world");
386 };
387}"#,
388 );
389 }
390
391 #[test]
392 fn test_remove_multi_dbg() {
393 check(r#"$0dbg!(0, 1)"#, r#"(0, 1)"#);
394 check(r#"$0dbg!(0, (1, 2))"#, r#"(0, (1, 2))"#);
395 }
396
397 #[test]
398 fn test_range() {
399 check(
400 r#"
401fn f() {
402 dbg!(0) + $0dbg!(1);
403 dbg!(())$0
404}
405"#,
406 r#"
407fn f() {
408 dbg!(0) + 1;
409 ()
410}
411"#,
412 );
413 }
414
415 #[test]
416 fn test_range_partial() {
417 check_assist_not_applicable(remove_dbg, r#"$0dbg$0!(0)"#);
418 check_assist_not_applicable(remove_dbg, r#"$0dbg!(0$0)"#);
419 }
420
421 #[test]
422 fn test_nested_dbg() {
423 check(
424 r#"$0let x = dbg!(dbg!(dbg!(dbg!(0 + 1)) * 2) + dbg!(3));$0"#,
425 r#"let x = ((0 + 1) * 2) + 3;"#,
426 );
427 check(r#"$0dbg!(10, dbg!(), dbg!(20, 30))$0"#, r#"(10, (), (20, 30))"#);
428 }
429
430 #[test]
431 fn test_multiple_nested_dbg() {
432 check(
433 r#"
434fn f() {
435 $0dbg!();
436 let x = dbg!(dbg!(dbg!(0 + 1)) + 2) + dbg!(3);
437 dbg!(10, dbg!(), dbg!(20, 30));$0
438}
439"#,
440 r#"
441fn f() {
442 let x = ((0 + 1) + 2) + 3;
443 (10, (), (20, 30));
444}
445"#,
446 );
447 }
448}