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