]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs
Rollup merge of #103996 - SUPERCILEX:docs, r=RalfJung
[rust.git] / src / tools / rust-analyzer / crates / ide-assists / src / handlers / remove_dbg.rs
1 use itertools::Itertools;
2 use syntax::{
3     ast::{self, AstNode, AstToken},
4     match_ast, NodeOrToken, SyntaxElement, TextSize, T,
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() {
15 //     $0dbg!(92);
16 // }
17 // ```
18 // ->
19 // ```
20 // fn main() {
21 //     92;
22 // }
23 // ```
24 pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
25     let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
26     let tt = macro_call.token_tree()?;
27     let r_delim = NodeOrToken::Token(tt.right_delimiter_token()?);
28     if macro_call.path()?.segment()?.name_ref()?.text() != "dbg"
29         || macro_call.excl_token().is_none()
30     {
31         return None;
32     }
33
34     let mac_input = tt.syntax().children_with_tokens().skip(1).take_while(|it| *it != r_delim);
35     let input_expressions = mac_input.group_by(|tok| tok.kind() == T![,]);
36     let input_expressions = input_expressions
37         .into_iter()
38         .filter_map(|(is_sep, group)| (!is_sep).then(|| group))
39         .map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join("")))
40         .collect::<Option<Vec<ast::Expr>>>()?;
41
42     let macro_expr = ast::MacroExpr::cast(macro_call.syntax().parent()?)?;
43     let parent = macro_expr.syntax().parent()?;
44     let (range, text) = match &*input_expressions {
45         // dbg!()
46         [] => {
47             match_ast! {
48                 match parent {
49                     ast::StmtList(__) => {
50                         let range = macro_expr.syntax().text_range();
51                         let range = match whitespace_start(macro_expr.syntax().prev_sibling_or_token()) {
52                             Some(start) => range.cover_offset(start),
53                             None => range,
54                         };
55                         (range, String::new())
56                     },
57                     ast::ExprStmt(it) => {
58                         let range = it.syntax().text_range();
59                         let range = match whitespace_start(it.syntax().prev_sibling_or_token()) {
60                             Some(start) => range.cover_offset(start),
61                             None => range,
62                         };
63                         (range, String::new())
64                     },
65                     _ => (macro_call.syntax().text_range(), "()".to_owned())
66                 }
67             }
68         }
69         // dbg!(expr0)
70         [expr] => {
71             let wrap = match ast::Expr::cast(parent) {
72                 Some(parent) => match (expr, parent) {
73                     (ast::Expr::CastExpr(_), ast::Expr::CastExpr(_)) => false,
74                     (
75                         ast::Expr::BoxExpr(_) | ast::Expr::PrefixExpr(_) | ast::Expr::RefExpr(_),
76                         ast::Expr::AwaitExpr(_)
77                         | ast::Expr::CallExpr(_)
78                         | ast::Expr::CastExpr(_)
79                         | ast::Expr::FieldExpr(_)
80                         | ast::Expr::IndexExpr(_)
81                         | ast::Expr::MethodCallExpr(_)
82                         | ast::Expr::RangeExpr(_)
83                         | ast::Expr::TryExpr(_),
84                     ) => true,
85                     (
86                         ast::Expr::BinExpr(_) | ast::Expr::CastExpr(_) | ast::Expr::RangeExpr(_),
87                         ast::Expr::AwaitExpr(_)
88                         | ast::Expr::BinExpr(_)
89                         | ast::Expr::CallExpr(_)
90                         | ast::Expr::CastExpr(_)
91                         | ast::Expr::FieldExpr(_)
92                         | ast::Expr::IndexExpr(_)
93                         | ast::Expr::MethodCallExpr(_)
94                         | ast::Expr::PrefixExpr(_)
95                         | ast::Expr::RangeExpr(_)
96                         | ast::Expr::RefExpr(_)
97                         | ast::Expr::TryExpr(_),
98                     ) => true,
99                     _ => false,
100                 },
101                 None => false,
102             };
103             (
104                 macro_call.syntax().text_range(),
105                 if wrap { format!("({expr})") } else { expr.to_string() },
106             )
107         }
108         // dbg!(expr0, expr1, ...)
109         exprs => (macro_call.syntax().text_range(), format!("({})", exprs.iter().format(", "))),
110     };
111
112     acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", range, |builder| {
113         builder.replace(range, text);
114     })
115 }
116
117 fn whitespace_start(it: Option<SyntaxElement>) -> Option<TextSize> {
118     Some(it?.into_token().and_then(ast::Whitespace::cast)?.syntax().text_range().start())
119 }
120
121 #[cfg(test)]
122 mod tests {
123     use crate::tests::{check_assist, check_assist_not_applicable};
124
125     use super::*;
126
127     fn check(ra_fixture_before: &str, ra_fixture_after: &str) {
128         check_assist(
129             remove_dbg,
130             &format!("fn main() {{\n{ra_fixture_before}\n}}"),
131             &format!("fn main() {{\n{ra_fixture_after}\n}}"),
132         );
133     }
134
135     #[test]
136     fn test_remove_dbg() {
137         check("$0dbg!(1 + 1)", "1 + 1");
138         check("dbg!$0(1 + 1)", "1 + 1");
139         check("dbg!(1 $0+ 1)", "1 + 1");
140         check("dbg![$01 + 1]", "1 + 1");
141         check("dbg!{$01 + 1}", "1 + 1");
142     }
143
144     #[test]
145     fn test_remove_dbg_not_applicable() {
146         check_assist_not_applicable(remove_dbg, "fn main() {$0vec![1, 2, 3]}");
147         check_assist_not_applicable(remove_dbg, "fn main() {$0dbg(5, 6, 7)}");
148         check_assist_not_applicable(remove_dbg, "fn main() {$0dbg!(5, 6, 7}");
149     }
150
151     #[test]
152     fn test_remove_dbg_keep_semicolon_in_let() {
153         // https://github.com/rust-lang/rust-analyzer/issues/5129#issuecomment-651399779
154         check(
155             r#"let res = $0dbg!(1 * 20); // needless comment"#,
156             r#"let res = 1 * 20; // needless comment"#,
157         );
158         check(r#"let res = $0dbg!(); // needless comment"#, r#"let res = (); // needless comment"#);
159         check(
160             r#"let res = $0dbg!(1, 2); // needless comment"#,
161             r#"let res = (1, 2); // needless comment"#,
162         );
163     }
164
165     #[test]
166     fn test_remove_dbg_cast_cast() {
167         check(r#"let res = $0dbg!(x as u32) as u32;"#, r#"let res = x as u32 as u32;"#);
168     }
169
170     #[test]
171     fn test_remove_dbg_prefix() {
172         check(r#"let res = $0dbg!(&result).foo();"#, r#"let res = (&result).foo();"#);
173         check(r#"let res = &$0dbg!(&result);"#, r#"let res = &&result;"#);
174         check(r#"let res = $0dbg!(!result) && true;"#, r#"let res = !result && true;"#);
175     }
176
177     #[test]
178     fn test_remove_dbg_post_expr() {
179         check(r#"let res = $0dbg!(fut.await).foo();"#, r#"let res = fut.await.foo();"#);
180         check(r#"let res = $0dbg!(result?).foo();"#, r#"let res = result?.foo();"#);
181         check(r#"let res = $0dbg!(foo as u32).foo();"#, r#"let res = (foo as u32).foo();"#);
182         check(r#"let res = $0dbg!(array[3]).foo();"#, r#"let res = array[3].foo();"#);
183         check(r#"let res = $0dbg!(tuple.3).foo();"#, r#"let res = tuple.3.foo();"#);
184     }
185
186     #[test]
187     fn test_remove_dbg_range_expr() {
188         check(r#"let res = $0dbg!(foo..bar).foo();"#, r#"let res = (foo..bar).foo();"#);
189         check(r#"let res = $0dbg!(foo..=bar).foo();"#, r#"let res = (foo..=bar).foo();"#);
190     }
191
192     #[test]
193     fn test_remove_empty_dbg() {
194         check_assist(remove_dbg, r#"fn foo() { $0dbg!(); }"#, r#"fn foo() { }"#);
195         check_assist(
196             remove_dbg,
197             r#"
198 fn foo() {
199     $0dbg!();
200 }
201 "#,
202             r#"
203 fn foo() {
204 }
205 "#,
206         );
207         check_assist(
208             remove_dbg,
209             r#"
210 fn foo() {
211     let test = $0dbg!();
212 }"#,
213             r#"
214 fn foo() {
215     let test = ();
216 }"#,
217         );
218         check_assist(
219             remove_dbg,
220             r#"
221 fn foo() {
222     let t = {
223         println!("Hello, world");
224         $0dbg!()
225     };
226 }"#,
227             r#"
228 fn foo() {
229     let t = {
230         println!("Hello, world");
231     };
232 }"#,
233         );
234     }
235
236     #[test]
237     fn test_remove_multi_dbg() {
238         check(r#"$0dbg!(0, 1)"#, r#"(0, 1)"#);
239         check(r#"$0dbg!(0, (1, 2))"#, r#"(0, (1, 2))"#);
240     }
241 }