]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | use itertools::Itertools; |
2 | use syntax::{ | |
3 | ast::{self, AstNode, AstToken}, | |
487cf647 | 4 | match_ast, NodeOrToken, SyntaxElement, TextRange, TextSize, T, |
064997fb FG |
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<()> { | |
487cf647 FG |
25 | let macro_calls = if ctx.has_empty_selection() { |
26 | vec![ctx.find_node_at_offset::<ast::MacroCall>()?] | |
27 | } else { | |
28 | ctx.covering_element() | |
29 | .as_node()? | |
30 | .descendants() | |
31 | .filter(|node| ctx.selection_trimmed().contains_range(node.text_range())) | |
32 | .filter_map(ast::MacroCall::cast) | |
33 | .collect() | |
34 | }; | |
35 | ||
36 | let replacements = | |
37 | macro_calls.into_iter().filter_map(compute_dbg_replacement).collect::<Vec<_>>(); | |
38 | if replacements.is_empty() { | |
39 | return None; | |
40 | } | |
41 | ||
42 | acc.add( | |
43 | AssistId("remove_dbg", AssistKind::Refactor), | |
44 | "Remove dbg!()", | |
45 | ctx.selection_trimmed(), | |
46 | |builder| { | |
47 | for (range, text) in replacements { | |
48 | builder.replace(range, text); | |
49 | } | |
50 | }, | |
51 | ) | |
52 | } | |
53 | ||
54 | fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, String)> { | |
064997fb FG |
55 | let tt = macro_call.token_tree()?; |
56 | let r_delim = NodeOrToken::Token(tt.right_delimiter_token()?); | |
57 | if macro_call.path()?.segment()?.name_ref()?.text() != "dbg" | |
58 | || macro_call.excl_token().is_none() | |
59 | { | |
60 | return None; | |
61 | } | |
62 | ||
63 | let mac_input = tt.syntax().children_with_tokens().skip(1).take_while(|it| *it != r_delim); | |
64 | let input_expressions = mac_input.group_by(|tok| tok.kind() == T![,]); | |
65 | let input_expressions = input_expressions | |
66 | .into_iter() | |
9c376795 | 67 | .filter_map(|(is_sep, group)| (!is_sep).then_some(group)) |
064997fb FG |
68 | .map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join(""))) |
69 | .collect::<Option<Vec<ast::Expr>>>()?; | |
70 | ||
71 | let macro_expr = ast::MacroExpr::cast(macro_call.syntax().parent()?)?; | |
72 | let parent = macro_expr.syntax().parent()?; | |
487cf647 | 73 | Some(match &*input_expressions { |
064997fb FG |
74 | // dbg!() |
75 | [] => { | |
76 | match_ast! { | |
77 | match parent { | |
78 | ast::StmtList(__) => { | |
79 | let range = macro_expr.syntax().text_range(); | |
80 | let range = match whitespace_start(macro_expr.syntax().prev_sibling_or_token()) { | |
81 | Some(start) => range.cover_offset(start), | |
82 | None => range, | |
83 | }; | |
84 | (range, String::new()) | |
85 | }, | |
86 | ast::ExprStmt(it) => { | |
87 | let range = it.syntax().text_range(); | |
88 | let range = match whitespace_start(it.syntax().prev_sibling_or_token()) { | |
89 | Some(start) => range.cover_offset(start), | |
90 | None => range, | |
91 | }; | |
92 | (range, String::new()) | |
93 | }, | |
94 | _ => (macro_call.syntax().text_range(), "()".to_owned()) | |
95 | } | |
96 | } | |
97 | } | |
98 | // dbg!(expr0) | |
99 | [expr] => { | |
100 | let wrap = match ast::Expr::cast(parent) { | |
101 | Some(parent) => match (expr, parent) { | |
102 | (ast::Expr::CastExpr(_), ast::Expr::CastExpr(_)) => false, | |
103 | ( | |
104 | ast::Expr::BoxExpr(_) | ast::Expr::PrefixExpr(_) | ast::Expr::RefExpr(_), | |
105 | ast::Expr::AwaitExpr(_) | |
106 | | ast::Expr::CallExpr(_) | |
107 | | ast::Expr::CastExpr(_) | |
108 | | ast::Expr::FieldExpr(_) | |
109 | | ast::Expr::IndexExpr(_) | |
110 | | ast::Expr::MethodCallExpr(_) | |
111 | | ast::Expr::RangeExpr(_) | |
112 | | ast::Expr::TryExpr(_), | |
113 | ) => true, | |
114 | ( | |
115 | ast::Expr::BinExpr(_) | ast::Expr::CastExpr(_) | ast::Expr::RangeExpr(_), | |
116 | ast::Expr::AwaitExpr(_) | |
117 | | ast::Expr::BinExpr(_) | |
118 | | ast::Expr::CallExpr(_) | |
119 | | ast::Expr::CastExpr(_) | |
120 | | ast::Expr::FieldExpr(_) | |
121 | | ast::Expr::IndexExpr(_) | |
122 | | ast::Expr::MethodCallExpr(_) | |
123 | | ast::Expr::PrefixExpr(_) | |
124 | | ast::Expr::RangeExpr(_) | |
125 | | ast::Expr::RefExpr(_) | |
126 | | ast::Expr::TryExpr(_), | |
127 | ) => true, | |
128 | _ => false, | |
129 | }, | |
130 | None => false, | |
131 | }; | |
132 | ( | |
133 | macro_call.syntax().text_range(), | |
487cf647 | 134 | if wrap { format!("({expr})") } else { expr.to_string() }, |
064997fb FG |
135 | ) |
136 | } | |
137 | // dbg!(expr0, expr1, ...) | |
138 | exprs => (macro_call.syntax().text_range(), format!("({})", exprs.iter().format(", "))), | |
064997fb FG |
139 | }) |
140 | } | |
141 | ||
142 | fn whitespace_start(it: Option<SyntaxElement>) -> Option<TextSize> { | |
143 | Some(it?.into_token().and_then(ast::Whitespace::cast)?.syntax().text_range().start()) | |
144 | } | |
145 | ||
146 | #[cfg(test)] | |
147 | mod tests { | |
148 | use crate::tests::{check_assist, check_assist_not_applicable}; | |
149 | ||
150 | use super::*; | |
151 | ||
152 | fn check(ra_fixture_before: &str, ra_fixture_after: &str) { | |
153 | check_assist( | |
154 | remove_dbg, | |
487cf647 FG |
155 | &format!("fn main() {{\n{ra_fixture_before}\n}}"), |
156 | &format!("fn main() {{\n{ra_fixture_after}\n}}"), | |
064997fb FG |
157 | ); |
158 | } | |
159 | ||
160 | #[test] | |
161 | fn test_remove_dbg() { | |
162 | check("$0dbg!(1 + 1)", "1 + 1"); | |
163 | check("dbg!$0(1 + 1)", "1 + 1"); | |
164 | check("dbg!(1 $0+ 1)", "1 + 1"); | |
165 | check("dbg![$01 + 1]", "1 + 1"); | |
166 | check("dbg!{$01 + 1}", "1 + 1"); | |
167 | } | |
168 | ||
169 | #[test] | |
170 | fn test_remove_dbg_not_applicable() { | |
171 | check_assist_not_applicable(remove_dbg, "fn main() {$0vec![1, 2, 3]}"); | |
172 | check_assist_not_applicable(remove_dbg, "fn main() {$0dbg(5, 6, 7)}"); | |
173 | check_assist_not_applicable(remove_dbg, "fn main() {$0dbg!(5, 6, 7}"); | |
174 | } | |
175 | ||
176 | #[test] | |
177 | fn test_remove_dbg_keep_semicolon_in_let() { | |
178 | // https://github.com/rust-lang/rust-analyzer/issues/5129#issuecomment-651399779 | |
179 | check( | |
180 | r#"let res = $0dbg!(1 * 20); // needless comment"#, | |
181 | r#"let res = 1 * 20; // needless comment"#, | |
182 | ); | |
183 | check(r#"let res = $0dbg!(); // needless comment"#, r#"let res = (); // needless comment"#); | |
184 | check( | |
185 | r#"let res = $0dbg!(1, 2); // needless comment"#, | |
186 | r#"let res = (1, 2); // needless comment"#, | |
187 | ); | |
188 | } | |
189 | ||
190 | #[test] | |
191 | fn test_remove_dbg_cast_cast() { | |
192 | check(r#"let res = $0dbg!(x as u32) as u32;"#, r#"let res = x as u32 as u32;"#); | |
193 | } | |
194 | ||
195 | #[test] | |
196 | fn test_remove_dbg_prefix() { | |
197 | check(r#"let res = $0dbg!(&result).foo();"#, r#"let res = (&result).foo();"#); | |
198 | check(r#"let res = &$0dbg!(&result);"#, r#"let res = &&result;"#); | |
199 | check(r#"let res = $0dbg!(!result) && true;"#, r#"let res = !result && true;"#); | |
200 | } | |
201 | ||
202 | #[test] | |
203 | fn test_remove_dbg_post_expr() { | |
204 | check(r#"let res = $0dbg!(fut.await).foo();"#, r#"let res = fut.await.foo();"#); | |
205 | check(r#"let res = $0dbg!(result?).foo();"#, r#"let res = result?.foo();"#); | |
206 | check(r#"let res = $0dbg!(foo as u32).foo();"#, r#"let res = (foo as u32).foo();"#); | |
207 | check(r#"let res = $0dbg!(array[3]).foo();"#, r#"let res = array[3].foo();"#); | |
208 | check(r#"let res = $0dbg!(tuple.3).foo();"#, r#"let res = tuple.3.foo();"#); | |
209 | } | |
210 | ||
211 | #[test] | |
212 | fn test_remove_dbg_range_expr() { | |
213 | check(r#"let res = $0dbg!(foo..bar).foo();"#, r#"let res = (foo..bar).foo();"#); | |
214 | check(r#"let res = $0dbg!(foo..=bar).foo();"#, r#"let res = (foo..=bar).foo();"#); | |
215 | } | |
216 | ||
217 | #[test] | |
218 | fn test_remove_empty_dbg() { | |
219 | check_assist(remove_dbg, r#"fn foo() { $0dbg!(); }"#, r#"fn foo() { }"#); | |
220 | check_assist( | |
221 | remove_dbg, | |
222 | r#" | |
223 | fn foo() { | |
224 | $0dbg!(); | |
225 | } | |
226 | "#, | |
227 | r#" | |
228 | fn foo() { | |
229 | } | |
230 | "#, | |
231 | ); | |
232 | check_assist( | |
233 | remove_dbg, | |
234 | r#" | |
235 | fn foo() { | |
236 | let test = $0dbg!(); | |
237 | }"#, | |
238 | r#" | |
239 | fn foo() { | |
240 | let test = (); | |
241 | }"#, | |
242 | ); | |
243 | check_assist( | |
244 | remove_dbg, | |
245 | r#" | |
246 | fn foo() { | |
247 | let t = { | |
248 | println!("Hello, world"); | |
249 | $0dbg!() | |
250 | }; | |
251 | }"#, | |
252 | r#" | |
253 | fn foo() { | |
254 | let t = { | |
255 | println!("Hello, world"); | |
256 | }; | |
257 | }"#, | |
258 | ); | |
259 | } | |
260 | ||
261 | #[test] | |
262 | fn test_remove_multi_dbg() { | |
263 | check(r#"$0dbg!(0, 1)"#, r#"(0, 1)"#); | |
264 | check(r#"$0dbg!(0, (1, 2))"#, r#"(0, (1, 2))"#); | |
265 | } | |
487cf647 FG |
266 | |
267 | #[test] | |
268 | fn test_range() { | |
269 | check( | |
270 | r#" | |
271 | fn f() { | |
272 | dbg!(0) + $0dbg!(1); | |
273 | dbg!(())$0 | |
274 | } | |
275 | "#, | |
276 | r#" | |
277 | fn f() { | |
278 | dbg!(0) + 1; | |
279 | () | |
280 | } | |
281 | "#, | |
282 | ); | |
283 | } | |
284 | ||
285 | #[test] | |
286 | fn test_range_partial() { | |
287 | check_assist_not_applicable(remove_dbg, r#"$0dbg$0!(0)"#); | |
288 | check_assist_not_applicable(remove_dbg, r#"$0dbg!(0$0)"#); | |
289 | } | |
064997fb | 290 | } |