]> git.lizzy.rs Git - rust.git/blob - crates/ra_assists/src/remove_dbg.rs
ra_assists: assist "providers" can produce multiple assists
[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(mut 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.add_action("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     ctx.build()
56 }
57
58 /// Verifies that the given macro_call actually matches the given name
59 /// and contains proper ending tokens
60 fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> {
61     let path = macro_call.path()?;
62     let name_ref = path.segment()?.name_ref()?;
63
64     // Make sure it is actually a dbg-macrocall, dbg followed by !
65     let excl = path.syntax().next_sibling()?;
66
67     if name_ref.text() != macro_name || excl.kind() != EXCL {
68         return None;
69     }
70
71     let node = macro_call.token_tree()?.syntax();
72     let first_child = node.first_child()?;
73     let last_child = node.last_child()?;
74
75     match (first_child.kind(), last_child.kind()) {
76         (L_PAREN, R_PAREN) | (L_BRACK, R_BRACK) | (L_CURLY, R_CURLY) => Some(true),
77         _ => Some(false),
78     }
79 }
80
81 #[cfg(test)]
82 mod tests {
83     use super::*;
84     use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
85
86     #[test]
87     fn test_remove_dbg() {
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, "dbg!(1 <|>+ 1)", "1 <|>+ 1");
93
94         check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = <|>1 + 1");
95
96         check_assist(
97             remove_dbg,
98             "
99 fn foo(n: usize) {
100     if let Some(_) = dbg!(n.<|>checked_sub(4)) {
101         // ...
102     }
103 }
104 ",
105             "
106 fn foo(n: usize) {
107     if let Some(_) = n.<|>checked_sub(4) {
108         // ...
109     }
110 }
111 ",
112         );
113     }
114     #[test]
115     fn test_remove_dbg_with_brackets_and_braces() {
116         check_assist(remove_dbg, "dbg![<|>1 + 1]", "<|>1 + 1");
117         check_assist(remove_dbg, "dbg!{<|>1 + 1}", "<|>1 + 1");
118     }
119
120     #[test]
121     fn test_remove_dbg_not_applicable() {
122         check_assist_not_applicable(remove_dbg, "<|>vec![1, 2, 3]");
123         check_assist_not_applicable(remove_dbg, "<|>dbg(5, 6, 7)");
124         check_assist_not_applicable(remove_dbg, "<|>dbg!(5, 6, 7");
125     }
126
127     #[test]
128     fn remove_dbg_target() {
129         check_assist_target(
130             remove_dbg,
131             "
132 fn foo(n: usize) {
133     if let Some(_) = dbg!(n.<|>checked_sub(4)) {
134         // ...
135     }
136 }
137 ",
138             "dbg!(n.checked_sub(4))",
139         );
140     }
141 }