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