]> git.proxmox.com Git - rustc.git/blame - src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs
New upstream version 1.70.0+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / ide-assists / src / handlers / remove_dbg.rs
CommitLineData
064997fb
FG
1use itertools::Itertools;
2use syntax::{
353b0b11
FG
3 ast::{self, make, AstNode, AstToken},
4 match_ast, ted, NodeOrToken, SyntaxElement, TextRange, TextSize, T,
064997fb
FG
5};
6
7use 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// ```
24pub(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.
68fn 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
166fn 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
208fn 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)]
213mod 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#"
289fn foo() {
290 $0dbg!();
291}
292"#,
293 r#"
294fn foo() {
295}
296"#,
297 );
298 check_assist(
299 remove_dbg,
300 r#"
301fn foo() {
302 let test = $0dbg!();
303}"#,
304 r#"
305fn foo() {
306 let test = ();
307}"#,
308 );
309 check_assist(
310 remove_dbg,
311 r#"
312fn foo() {
313 let t = {
314 println!("Hello, world");
315 $0dbg!()
316 };
317}"#,
318 r#"
319fn 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#"
337fn f() {
338 dbg!(0) + $0dbg!(1);
339 dbg!(())$0
340}
341"#,
342 r#"
343fn 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#"
370fn 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#"
377fn f() {
378 let x = ((0 + 1) + 2) + 3;
379 (10, (), (20, 30));
380}
381"#,
382 );
383 }
064997fb 384}