ra_ap_ide_assists/handlers/
remove_dbg.rs1use 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(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join(""), Edition::CURRENT))
87 .collect::<Option<Vec<ast::Expr>>>()?;
88
89 let parent = macro_expr.syntax().parent()?;
90 Some(match &*input_expressions {
91 [] => {
93 match_ast! {
94 match parent {
95 ast::StmtList(_) => {
96 let mut replace = vec![macro_expr.syntax().clone().into()];
97 if let Some(prev_sibling) = macro_expr.syntax().prev_sibling_or_token()
98 && prev_sibling.kind() == syntax::SyntaxKind::WHITESPACE {
99 replace.push(prev_sibling);
100 }
101 (replace, None)
102 },
103 ast::ExprStmt(it) => {
104 let mut replace = vec![it.syntax().clone().into()];
105 if let Some(prev_sibling) = it.syntax().prev_sibling_or_token()
106 && prev_sibling.kind() == syntax::SyntaxKind::WHITESPACE {
107 replace.push(prev_sibling);
108 }
109 (replace, None)
110 },
111 _ => (vec![macro_call.syntax().clone().into()], Some(make::ext::expr_unit())),
112 }
113 }
114 }
115 exprs if ast::ExprStmt::can_cast(parent.kind()) && exprs.iter().all(pure_expr) => {
117 let mut replace = vec![parent.clone().into()];
118 if let Some(prev_sibling) = parent.prev_sibling_or_token()
119 && prev_sibling.kind() == syntax::SyntaxKind::WHITESPACE
120 {
121 replace.push(prev_sibling);
122 }
123 (replace, None)
124 }
125 [expr] => {
127 let wrap = match ast::Expr::cast(parent) {
129 Some(parent) => match (expr, parent) {
130 (ast::Expr::CastExpr(_), ast::Expr::CastExpr(_)) => false,
131 (
132 ast::Expr::PrefixExpr(_) | ast::Expr::RefExpr(_) | ast::Expr::MacroExpr(_),
133 ast::Expr::AwaitExpr(_)
134 | ast::Expr::CallExpr(_)
135 | ast::Expr::CastExpr(_)
136 | ast::Expr::FieldExpr(_)
137 | ast::Expr::IndexExpr(_)
138 | ast::Expr::MethodCallExpr(_)
139 | ast::Expr::RangeExpr(_)
140 | ast::Expr::TryExpr(_),
141 ) => true,
142 (
143 ast::Expr::BinExpr(_)
144 | ast::Expr::CastExpr(_)
145 | ast::Expr::RangeExpr(_)
146 | ast::Expr::MacroExpr(_),
147 ast::Expr::AwaitExpr(_)
148 | ast::Expr::BinExpr(_)
149 | ast::Expr::CallExpr(_)
150 | ast::Expr::CastExpr(_)
151 | ast::Expr::FieldExpr(_)
152 | ast::Expr::IndexExpr(_)
153 | ast::Expr::MethodCallExpr(_)
154 | ast::Expr::PrefixExpr(_)
155 | ast::Expr::RangeExpr(_)
156 | ast::Expr::RefExpr(_)
157 | ast::Expr::TryExpr(_),
158 ) => true,
159 _ => false,
160 },
161 None => false,
162 };
163 let expr = replace_nested_dbgs(expr.clone());
164 let expr = if wrap { make::expr_paren(expr).into() } else { expr.clone_subtree() };
165 (vec![macro_call.syntax().clone().into()], Some(expr))
166 }
167 exprs => {
169 let exprs = exprs.iter().cloned().map(replace_nested_dbgs);
170 let expr = make::expr_tuple(exprs);
171 (vec![macro_call.syntax().clone().into()], Some(expr.into()))
172 }
173 })
174}
175
176fn pure_expr(expr: &ast::Expr) -> bool {
177 match_ast! {
178 match (expr.syntax()) {
179 ast::Literal(_) => true,
180 ast::RefExpr(it) => {
181 matches!(it.expr(), Some(ast::Expr::PathExpr(p))
182 if p.path().and_then(|p| p.as_single_name_ref()).is_some())
183 },
184 ast::PathExpr(it) => it.path().and_then(|it| it.as_single_name_ref()).is_some(),
185 _ => false,
186 }
187 }
188}
189
190fn replace_nested_dbgs(expanded: ast::Expr) -> ast::Expr {
191 if let ast::Expr::MacroExpr(mac) = &expanded {
192 let replaced = if let Some((_, expr_opt)) = compute_dbg_replacement(mac.clone()) {
196 match expr_opt {
197 Some(expr) => expr,
198 None => {
199 stdx::never!("dbg! inside dbg! should not be just removed");
200 expanded
201 }
202 }
203 } else {
204 expanded
205 };
206
207 return replaced;
208 }
209
210 let expanded = expanded.clone_subtree();
211 let mut editor = SyntaxEditor::new(expanded.syntax().clone());
212 let macro_exprs: Vec<_> =
214 expanded.syntax().descendants().filter_map(ast::MacroExpr::cast).collect();
215
216 for mac in macro_exprs {
217 let expr_opt = match compute_dbg_replacement(mac.clone()) {
218 Some((_, expr)) => expr,
219 None => continue,
220 };
221
222 if let Some(expr) = expr_opt {
223 editor.replace(mac.syntax(), expr.syntax().clone_for_update());
224 } else {
225 editor.delete(mac.syntax());
226 }
227 }
228 let expanded_syntax = editor.finish().new_root().clone();
229 ast::Expr::cast(expanded_syntax).unwrap()
230}
231
232#[cfg(test)]
233mod tests {
234 use crate::tests::{check_assist, check_assist_not_applicable};
235
236 use super::*;
237
238 fn check(
239 #[rust_analyzer::rust_fixture] ra_fixture_before: &str,
240 #[rust_analyzer::rust_fixture] ra_fixture_after: &str,
241 ) {
242 check_assist(
243 remove_dbg,
244 &format!("fn main() {{\n{ra_fixture_before}\n}}"),
245 &format!("fn main() {{\n{ra_fixture_after}\n}}"),
246 );
247 }
248
249 #[test]
250 fn test_remove_dbg() {
251 check("$0dbg!(1 + 1)", "1 + 1");
252 check("dbg!$0(1 + 1)", "1 + 1");
253 check("dbg!(1 $0+ 1)", "1 + 1");
254 check("dbg![$01 + 1]", "1 + 1");
255 check("dbg!{$01 + 1}", "1 + 1");
256 }
257
258 #[test]
259 fn test_remove_simple_dbg_statement() {
260 check_assist(
261 remove_dbg,
262 r#"
263fn foo() {
264 let n = 2;
265 $0dbg!(3);
266 dbg!(2.6);
267 dbg!(1, 2.5);
268 dbg!('x');
269 dbg!(&n);
270 dbg!(n);
271 // needless comment
272 dbg!("foo");$0
273}
274"#,
275 r#"
276fn foo() {
277 let n = 2;
278 // needless comment
279}
280"#,
281 );
282 }
283
284 #[test]
285 fn test_remove_dbg_not_applicable() {
286 check_assist_not_applicable(remove_dbg, "fn main() {$0vec![1, 2, 3]}");
287 check_assist_not_applicable(remove_dbg, "fn main() {$0dbg(5, 6, 7)}");
288 check_assist_not_applicable(remove_dbg, "fn main() {$0dbg!(5, 6, 7}");
289 }
290
291 #[test]
292 fn test_remove_dbg_keep_semicolon_in_let() {
293 check(
295 r#"let res = $0dbg!(1 * 20); // needless comment"#,
296 r#"let res = 1 * 20; // needless comment"#,
297 );
298 check(r#"let res = $0dbg!(); // needless comment"#, r#"let res = (); // needless comment"#);
299 check(
300 r#"let res = $0dbg!(1, 2); // needless comment"#,
301 r#"let res = (1, 2); // needless comment"#,
302 );
303 }
304
305 #[test]
306 fn test_remove_dbg_cast_cast() {
307 check(r#"let res = $0dbg!(x as u32) as u32;"#, r#"let res = x as u32 as u32;"#);
308 }
309
310 #[test]
311 fn test_remove_dbg_prefix() {
312 check(r#"let res = $0dbg!(&result).foo();"#, r#"let res = (&result).foo();"#);
313 check(r#"let res = &$0dbg!(&result);"#, r#"let res = &&result;"#);
314 check(r#"let res = $0dbg!(!result) && true;"#, r#"let res = !result && true;"#);
315 }
316
317 #[test]
318 fn test_remove_dbg_post_expr() {
319 check(r#"let res = $0dbg!(fut.await).foo();"#, r#"let res = fut.await.foo();"#);
320 check(r#"let res = $0dbg!(result?).foo();"#, r#"let res = result?.foo();"#);
321 check(r#"let res = $0dbg!(foo as u32).foo();"#, r#"let res = (foo as u32).foo();"#);
322 check(r#"let res = $0dbg!(array[3]).foo();"#, r#"let res = array[3].foo();"#);
323 check(r#"let res = $0dbg!(tuple.3).foo();"#, r#"let res = tuple.3.foo();"#);
324 }
325
326 #[test]
327 fn test_remove_dbg_range_expr() {
328 check(r#"let res = $0dbg!(foo..bar).foo();"#, r#"let res = (foo..bar).foo();"#);
329 check(r#"let res = $0dbg!(foo..=bar).foo();"#, r#"let res = (foo..=bar).foo();"#);
330 }
331
332 #[test]
333 fn test_remove_empty_dbg() {
334 check_assist(remove_dbg, r#"fn foo() { $0dbg!(); }"#, r#"fn foo() { }"#);
335 check_assist(
336 remove_dbg,
337 r#"
338fn foo() {
339 $0dbg!();
340}
341"#,
342 r#"
343fn foo() {
344}
345"#,
346 );
347 check_assist(
348 remove_dbg,
349 r#"
350fn foo() {
351 let test = $0dbg!();
352}"#,
353 r#"
354fn foo() {
355 let test = ();
356}"#,
357 );
358 check_assist(
359 remove_dbg,
360 r#"
361fn foo() {
362 let t = {
363 println!("Hello, world");
364 $0dbg!()
365 };
366}"#,
367 r#"
368fn foo() {
369 let t = {
370 println!("Hello, world");
371 };
372}"#,
373 );
374 }
375
376 #[test]
377 fn test_remove_multi_dbg() {
378 check(r#"$0dbg!(0, 1)"#, r#"(0, 1)"#);
379 check(r#"$0dbg!(0, (1, 2))"#, r#"(0, (1, 2))"#);
380 }
381
382 #[test]
383 fn test_range() {
384 check(
385 r#"
386fn f() {
387 dbg!(0) + $0dbg!(1);
388 dbg!(())$0
389}
390"#,
391 r#"
392fn f() {
393 dbg!(0) + 1;
394 ()
395}
396"#,
397 );
398 }
399
400 #[test]
401 fn test_range_partial() {
402 check_assist_not_applicable(remove_dbg, r#"$0dbg$0!(0)"#);
403 check_assist_not_applicable(remove_dbg, r#"$0dbg!(0$0)"#);
404 }
405
406 #[test]
407 fn test_nested_dbg() {
408 check(
409 r#"$0let x = dbg!(dbg!(dbg!(dbg!(0 + 1)) * 2) + dbg!(3));$0"#,
410 r#"let x = ((0 + 1) * 2) + 3;"#,
411 );
412 check(r#"$0dbg!(10, dbg!(), dbg!(20, 30))$0"#, r#"(10, (), (20, 30))"#);
413 }
414
415 #[test]
416 fn test_multiple_nested_dbg() {
417 check(
418 r#"
419fn f() {
420 $0dbg!();
421 let x = dbg!(dbg!(dbg!(0 + 1)) + 2) + dbg!(3);
422 dbg!(10, dbg!(), dbg!(20, 30));$0
423}
424"#,
425 r#"
426fn f() {
427 let x = ((0 + 1) + 2) + 3;
428 (10, (), (20, 30));
429}
430"#,
431 );
432 }
433}