Skip to content

Commit 208d460

Browse files
Use better heuristics for replacement text when removing dbg!
1 parent 5c336e2 commit 208d460

File tree

1 file changed

+116
-37
lines changed

1 file changed

+116
-37
lines changed

crates/assists/src/handlers/remove_dbg.rs

Lines changed: 116 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use syntax::{
22
ast::{self, AstNode},
3-
TextRange, TextSize, T,
3+
SyntaxElement, TextRange, TextSize, T,
44
};
55

66
use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -22,64 +22,120 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
2222
// ```
2323
pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
2424
let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
25+
let new_contents = adjusted_macro_contents(&macro_call)?;
2526

26-
if !is_valid_macrocall(&macro_call, "dbg")? {
27-
return None;
28-
}
29-
30-
let is_leaf = macro_call.syntax().next_sibling().is_none();
31-
27+
let macro_text_range = macro_call.syntax().text_range();
3228
let macro_end = if macro_call.semicolon_token().is_some() {
33-
macro_call.syntax().text_range().end() - TextSize::of(';')
29+
macro_text_range.end() - TextSize::of(';')
3430
} else {
35-
macro_call.syntax().text_range().end()
31+
macro_text_range.end()
3632
};
3733

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-
};
34+
acc.add(
35+
AssistId("remove_dbg", AssistKind::Refactor),
36+
"Remove dbg!()",
37+
macro_text_range,
38+
|builder| {
39+
builder.replace(TextRange::new(macro_text_range.start(), macro_end), new_contents);
40+
},
41+
)
42+
}
5343

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-
})
44+
fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> {
45+
let contents = get_valid_macrocall_contents(&macro_call, "dbg")?;
46+
let is_leaf = macro_call.syntax().next_sibling().is_none();
47+
let macro_text_with_brackets = macro_call.token_tree()?.syntax().text();
48+
let slice_index = if is_leaf || !needs_parentheses_around_macro_contents(contents) {
49+
TextRange::new(TextSize::of('('), macro_text_with_brackets.len() - TextSize::of(')'))
50+
} else {
51+
// leave parenthesis around macro contents to preserve the semantics
52+
TextRange::up_to(macro_text_with_brackets.len())
53+
};
54+
Some(macro_text_with_brackets.slice(slice_index).to_string())
5855
}
5956

6057
/// 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> {
58+
/// and contains proper ending tokens, then returns the contents between the ending tokens
59+
fn get_valid_macrocall_contents(
60+
macro_call: &ast::MacroCall,
61+
macro_name: &str,
62+
) -> Option<Vec<SyntaxElement>> {
6363
let path = macro_call.path()?;
6464
let name_ref = path.segment()?.name_ref()?;
6565

6666
// Make sure it is actually a dbg-macro call, dbg followed by !
6767
let excl = path.syntax().next_sibling_or_token()?;
68-
6968
if name_ref.text() != macro_name || excl.kind() != T![!] {
7069
return None;
7170
}
7271

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()?;
72+
let mut children_with_tokens = macro_call.token_tree()?.syntax().children_with_tokens();
73+
let first_child = children_with_tokens.next()?;
74+
let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>();
75+
let last_child = contents_between_brackets.pop()?;
7676

77-
match (first_child.kind(), last_child.kind()) {
78-
(T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true),
79-
_ => Some(false),
77+
if contents_between_brackets.is_empty() {
78+
None
79+
} else {
80+
match (first_child.kind(), last_child.kind()) {
81+
(T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => {
82+
Some(contents_between_brackets)
83+
}
84+
_ => None,
85+
}
8086
}
8187
}
8288

89+
fn needs_parentheses_around_macro_contents(macro_contents: Vec<SyntaxElement>) -> bool {
90+
if macro_contents.len() < 2 {
91+
return false;
92+
}
93+
94+
let mut macro_contents_kind_not_in_brackets = Vec::with_capacity(macro_contents.len());
95+
96+
let mut first_bracket_in_macro = None;
97+
let mut unpaired_brackets_in_contents = Vec::new();
98+
for element in macro_contents {
99+
match element.kind() {
100+
T!['('] | T!['['] | T!['{'] => {
101+
if let None = first_bracket_in_macro {
102+
first_bracket_in_macro = Some(element.clone())
103+
}
104+
unpaired_brackets_in_contents.push(element);
105+
}
106+
T![')'] => {
107+
if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['('])
108+
{
109+
return true;
110+
}
111+
}
112+
T![']'] => {
113+
if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['['])
114+
{
115+
return true;
116+
}
117+
}
118+
T!['}'] => {
119+
if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['{'])
120+
{
121+
return true;
122+
}
123+
}
124+
other_kind => {
125+
if unpaired_brackets_in_contents.is_empty() {
126+
macro_contents_kind_not_in_brackets.push(other_kind);
127+
}
128+
}
129+
}
130+
}
131+
132+
!unpaired_brackets_in_contents.is_empty()
133+
|| matches!(first_bracket_in_macro, Some(bracket) if bracket.kind() != T!['('])
134+
|| macro_contents_kind_not_in_brackets
135+
.into_iter()
136+
.any(|macro_contents_kind| macro_contents_kind.is_punct())
137+
}
138+
83139
#[cfg(test)]
84140
mod tests {
85141
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
@@ -156,6 +212,29 @@ fn foo(n: usize) {
156212
);
157213
}
158214

215+
#[test]
216+
fn remove_dbg_from_non_leaf_simple_expression() {
217+
check_assist(
218+
remove_dbg,
219+
"
220+
fn main() {
221+
let mut a = 1;
222+
while dbg!<|>(a) < 10000 {
223+
a += 1;
224+
}
225+
}
226+
",
227+
"
228+
fn main() {
229+
let mut a = 1;
230+
while a < 10000 {
231+
a += 1;
232+
}
233+
}
234+
",
235+
);
236+
}
237+
159238
#[test]
160239
fn test_remove_dbg_keep_expression() {
161240
check_assist(

0 commit comments

Comments
 (0)