]> git.lizzy.rs Git - rust.git/blob - crates/ra_assists/src/remove_dbg.rs
e9d0a635b3ed9563908e40494e55309745b9d47e
[rust.git] / crates / ra_assists / src / remove_dbg.rs
1 use hir::db::HirDatabase;
2 use ra_syntax::{
3     ast::{self, AstNode},
4     TextUnit,
5     SyntaxKind::{
6         L_PAREN, R_PAREN, L_CURLY, R_CURLY, L_BRACK, R_BRACK, EXCL
7     },
8 };
9 use crate::{AssistCtx, Assist};
10
11 pub(crate) fn remove_dbg(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
12     let macro_call = ctx.node_at_offset::<ast::MacroCall>()?;
13
14     if !is_valid_macrocall(macro_call, "dbg")? {
15         return None;
16     }
17
18     let macro_range = macro_call.syntax().range();
19
20     // If the cursor is inside the macrocall, we'll try to maintain
21     // the cursor position by subtracting the length of dbg!( from the start
22     // of the filerange, otherwise we'll default to using the start of the macrocall
23     let cursor_pos = {
24         let file_range = ctx.frange.range;
25
26         let offset_start = file_range
27             .start()
28             .checked_sub(macro_range.start())
29             .unwrap_or_else(|| TextUnit::from(0));
30
31         let dbg_size = TextUnit::of_str("dbg!(");
32
33         if offset_start > dbg_size {
34             file_range.start() - dbg_size
35         } else {
36             macro_range.start()
37         }
38     };
39
40     let macro_content = {
41         let macro_args = macro_call.token_tree()?.syntax();
42         let range = macro_args.range();
43         let start = range.start() + TextUnit::of_char('(');
44         let end = range.end() - TextUnit::of_char(')');
45
46         macro_args.text().slice(start..end).to_string()
47     };
48
49     ctx.build("remove dbg!()", |edit| {
50         edit.target(macro_call.syntax().range());
51         edit.replace(macro_range, macro_content);
52         edit.set_cursor(cursor_pos);
53     })
54 }
55
56 /// Verifies that the given macro_call actually matches the given name
57 /// and contains proper ending tokens
58 fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> {
59     let path = macro_call.path()?;
60     let name_ref = path.segment()?.name_ref()?;
61
62     // Make sure it is actually a dbg-macrocall, dbg followed by !
63     let excl = path.syntax().next_sibling()?;
64
65     if name_ref.text() != macro_name || excl.kind() != EXCL {
66         return None;
67     }
68
69     let node = macro_call.token_tree()?.syntax();
70     let first_child = node.first_child()?;
71     let last_child = node.last_child()?;
72
73     match (first_child.kind(), last_child.kind()) {
74         (L_PAREN, R_PAREN) | (L_BRACK, R_BRACK) | (L_CURLY, R_CURLY) => Some(true),
75         _ => Some(false),
76     }
77 }
78
79 #[cfg(test)]
80 mod tests {
81     use super::*;
82     use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
83
84     #[test]
85     fn test_remove_dbg() {
86         check_assist(remove_dbg, "<|>dbg!(1 + 1)", "<|>1 + 1");
87
88         check_assist(remove_dbg, "dbg!<|>((1 + 1))", "<|>(1 + 1)");
89
90         check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 <|>+ 1");
91
92         check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = <|>1 + 1");
93
94         check_assist(
95             remove_dbg,
96             "
97 fn foo(n: usize) {
98     if let Some(_) = dbg!(n.<|>checked_sub(4)) {
99         // ...
100     }
101 }
102 ",
103             "
104 fn foo(n: usize) {
105     if let Some(_) = n.<|>checked_sub(4) {
106         // ...
107     }
108 }
109 ",
110         );
111     }
112     #[test]
113     fn test_remove_dbg_with_brackets_and_braces() {
114         check_assist(remove_dbg, "dbg![<|>1 + 1]", "<|>1 + 1");
115         check_assist(remove_dbg, "dbg!{<|>1 + 1}", "<|>1 + 1");
116     }
117
118     #[test]
119     fn test_remove_dbg_not_applicable() {
120         check_assist_not_applicable(remove_dbg, "<|>vec![1, 2, 3]");
121         check_assist_not_applicable(remove_dbg, "<|>dbg(5, 6, 7)");
122         check_assist_not_applicable(remove_dbg, "<|>dbg!(5, 6, 7");
123     }
124
125     #[test]
126     fn remove_dbg_target() {
127         check_assist_target(
128             remove_dbg,
129             "
130 fn foo(n: usize) {
131     if let Some(_) = dbg!(n.<|>checked_sub(4)) {
132         // ...
133     }
134 }
135 ",
136             "dbg!(n.checked_sub(4))",
137         );
138     }
139 }