diff options
Diffstat (limited to 'crates/assists/src/handlers/remove_dbg.rs')
-rw-r--r-- | crates/assists/src/handlers/remove_dbg.rs | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/remove_dbg.rs b/crates/assists/src/handlers/remove_dbg.rs new file mode 100644 index 000000000..f3dcca534 --- /dev/null +++ b/crates/assists/src/handlers/remove_dbg.rs | |||
@@ -0,0 +1,205 @@ | |||
1 | use syntax::{ | ||
2 | ast::{self, AstNode}, | ||
3 | TextRange, TextSize, T, | ||
4 | }; | ||
5 | |||
6 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | ||
7 | |||
8 | // Assist: remove_dbg | ||
9 | // | ||
10 | // Removes `dbg!()` macro call. | ||
11 | // | ||
12 | // ``` | ||
13 | // fn main() { | ||
14 | // <|>dbg!(92); | ||
15 | // } | ||
16 | // ``` | ||
17 | // -> | ||
18 | // ``` | ||
19 | // fn main() { | ||
20 | // 92; | ||
21 | // } | ||
22 | // ``` | ||
23 | pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
24 | let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; | ||
25 | |||
26 | if !is_valid_macrocall(¯o_call, "dbg")? { | ||
27 | return None; | ||
28 | } | ||
29 | |||
30 | let is_leaf = macro_call.syntax().next_sibling().is_none(); | ||
31 | |||
32 | let macro_end = if macro_call.semicolon_token().is_some() { | ||
33 | macro_call.syntax().text_range().end() - TextSize::of(';') | ||
34 | } else { | ||
35 | macro_call.syntax().text_range().end() | ||
36 | }; | ||
37 | |||
38 | // macro_range determines what will be deleted and replaced with macro_content | ||
39 | let macro_range = TextRange::new(macro_call.syntax().text_range().start(), macro_end); | ||
40 | let paste_instead_of_dbg = { | ||
41 | let text = macro_call.token_tree()?.syntax().text(); | ||
42 | |||
43 | // leafiness determines if we should include the parenthesis or not | ||
44 | let slice_index: TextRange = if is_leaf { | ||
45 | // leaf means - we can extract the contents of the dbg! in text | ||
46 | TextRange::new(TextSize::of('('), text.len() - TextSize::of(')')) | ||
47 | } else { | ||
48 | // not leaf - means we should keep the parens | ||
49 | TextRange::up_to(text.len()) | ||
50 | }; | ||
51 | text.slice(slice_index).to_string() | ||
52 | }; | ||
53 | |||
54 | let target = macro_call.syntax().text_range(); | ||
55 | acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", target, |builder| { | ||
56 | builder.replace(macro_range, paste_instead_of_dbg); | ||
57 | }) | ||
58 | } | ||
59 | |||
60 | /// Verifies that the given macro_call actually matches the given name | ||
61 | /// and contains proper ending tokens | ||
62 | fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> { | ||
63 | let path = macro_call.path()?; | ||
64 | let name_ref = path.segment()?.name_ref()?; | ||
65 | |||
66 | // Make sure it is actually a dbg-macro call, dbg followed by ! | ||
67 | let excl = path.syntax().next_sibling_or_token()?; | ||
68 | |||
69 | if name_ref.text() != macro_name || excl.kind() != T![!] { | ||
70 | return None; | ||
71 | } | ||
72 | |||
73 | let node = macro_call.token_tree()?.syntax().clone(); | ||
74 | let first_child = node.first_child_or_token()?; | ||
75 | let last_child = node.last_child_or_token()?; | ||
76 | |||
77 | match (first_child.kind(), last_child.kind()) { | ||
78 | (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true), | ||
79 | _ => Some(false), | ||
80 | } | ||
81 | } | ||
82 | |||
83 | #[cfg(test)] | ||
84 | mod tests { | ||
85 | use super::*; | ||
86 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
87 | |||
88 | #[test] | ||
89 | fn test_remove_dbg() { | ||
90 | check_assist(remove_dbg, "<|>dbg!(1 + 1)", "1 + 1"); | ||
91 | |||
92 | check_assist(remove_dbg, "dbg!<|>((1 + 1))", "(1 + 1)"); | ||
93 | |||
94 | check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 + 1"); | ||
95 | |||
96 | check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = 1 + 1"); | ||
97 | |||
98 | check_assist( | ||
99 | remove_dbg, | ||
100 | " | ||
101 | fn foo(n: usize) { | ||
102 | if let Some(_) = dbg!(n.<|>checked_sub(4)) { | ||
103 | // ... | ||
104 | } | ||
105 | } | ||
106 | ", | ||
107 | " | ||
108 | fn foo(n: usize) { | ||
109 | if let Some(_) = n.checked_sub(4) { | ||
110 | // ... | ||
111 | } | ||
112 | } | ||
113 | ", | ||
114 | ); | ||
115 | } | ||
116 | |||
117 | #[test] | ||
118 | fn test_remove_dbg_with_brackets_and_braces() { | ||
119 | check_assist(remove_dbg, "dbg![<|>1 + 1]", "1 + 1"); | ||
120 | check_assist(remove_dbg, "dbg!{<|>1 + 1}", "1 + 1"); | ||
121 | } | ||
122 | |||
123 | #[test] | ||
124 | fn test_remove_dbg_not_applicable() { | ||
125 | check_assist_not_applicable(remove_dbg, "<|>vec![1, 2, 3]"); | ||
126 | check_assist_not_applicable(remove_dbg, "<|>dbg(5, 6, 7)"); | ||
127 | check_assist_not_applicable(remove_dbg, "<|>dbg!(5, 6, 7"); | ||
128 | } | ||
129 | |||
130 | #[test] | ||
131 | fn test_remove_dbg_target() { | ||
132 | check_assist_target( | ||
133 | remove_dbg, | ||
134 | " | ||
135 | fn foo(n: usize) { | ||
136 | if let Some(_) = dbg!(n.<|>checked_sub(4)) { | ||
137 | // ... | ||
138 | } | ||
139 | } | ||
140 | ", | ||
141 | "dbg!(n.checked_sub(4))", | ||
142 | ); | ||
143 | } | ||
144 | |||
145 | #[test] | ||
146 | fn test_remove_dbg_keep_semicolon() { | ||
147 | // https://github.com/rust-analyzer/rust-analyzer/issues/5129#issuecomment-651399779 | ||
148 | // not quite though | ||
149 | // adding a comment at the end of the line makes | ||
150 | // the ast::MacroCall to include the semicolon at the end | ||
151 | check_assist( | ||
152 | remove_dbg, | ||
153 | r#"let res = <|>dbg!(1 * 20); // needless comment"#, | ||
154 | r#"let res = 1 * 20; // needless comment"#, | ||
155 | ); | ||
156 | } | ||
157 | |||
158 | #[test] | ||
159 | fn test_remove_dbg_keep_expression() { | ||
160 | check_assist( | ||
161 | remove_dbg, | ||
162 | r#"let res = <|>dbg!(a + b).foo();"#, | ||
163 | r#"let res = (a + b).foo();"#, | ||
164 | ); | ||
165 | } | ||
166 | |||
167 | #[test] | ||
168 | fn test_remove_dbg_from_inside_fn() { | ||
169 | check_assist_target( | ||
170 | remove_dbg, | ||
171 | r#" | ||
172 | fn square(x: u32) -> u32 { | ||
173 | x * x | ||
174 | } | ||
175 | |||
176 | fn main() { | ||
177 | let x = square(dbg<|>!(5 + 10)); | ||
178 | println!("{}", x); | ||
179 | }"#, | ||
180 | "dbg!(5 + 10)", | ||
181 | ); | ||
182 | |||
183 | check_assist( | ||
184 | remove_dbg, | ||
185 | r#" | ||
186 | fn square(x: u32) -> u32 { | ||
187 | x * x | ||
188 | } | ||
189 | |||
190 | fn main() { | ||
191 | let x = square(dbg<|>!(5 + 10)); | ||
192 | println!("{}", x); | ||
193 | }"#, | ||
194 | r#" | ||
195 | fn square(x: u32) -> u32 { | ||
196 | x * x | ||
197 | } | ||
198 | |||
199 | fn main() { | ||
200 | let x = square(5 + 10); | ||
201 | println!("{}", x); | ||
202 | }"#, | ||
203 | ); | ||
204 | } | ||
205 | } | ||