From 208d4609b0f0168536074135058504f9e7de8932 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 10 Sep 2020 14:14:24 +0300 Subject: Use better heuristics for replacement text when removing dbg! --- crates/assists/src/handlers/remove_dbg.rs | 153 ++++++++++++++++++++++-------- 1 file changed, 116 insertions(+), 37 deletions(-) (limited to 'crates/assists') diff --git a/crates/assists/src/handlers/remove_dbg.rs b/crates/assists/src/handlers/remove_dbg.rs index 4e252edf0..9397efd63 100644 --- a/crates/assists/src/handlers/remove_dbg.rs +++ b/crates/assists/src/handlers/remove_dbg.rs @@ -1,6 +1,6 @@ use syntax::{ ast::{self, AstNode}, - TextRange, TextSize, T, + SyntaxElement, TextRange, TextSize, T, }; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -22,64 +22,120 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; // ``` pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let macro_call = ctx.find_node_at_offset::()?; + let new_contents = adjusted_macro_contents(¯o_call)?; - if !is_valid_macrocall(¯o_call, "dbg")? { - return None; - } - - let is_leaf = macro_call.syntax().next_sibling().is_none(); - + let macro_text_range = macro_call.syntax().text_range(); let macro_end = if macro_call.semicolon_token().is_some() { - macro_call.syntax().text_range().end() - TextSize::of(';') + macro_text_range.end() - TextSize::of(';') } else { - macro_call.syntax().text_range().end() + macro_text_range.end() }; - // macro_range determines what will be deleted and replaced with macro_content - let macro_range = TextRange::new(macro_call.syntax().text_range().start(), macro_end); - let paste_instead_of_dbg = { - let text = macro_call.token_tree()?.syntax().text(); - - // leafiness determines if we should include the parenthesis or not - let slice_index: TextRange = if is_leaf { - // leaf means - we can extract the contents of the dbg! in text - TextRange::new(TextSize::of('('), text.len() - TextSize::of(')')) - } else { - // not leaf - means we should keep the parens - TextRange::up_to(text.len()) - }; - text.slice(slice_index).to_string() - }; + acc.add( + AssistId("remove_dbg", AssistKind::Refactor), + "Remove dbg!()", + macro_text_range, + |builder| { + builder.replace(TextRange::new(macro_text_range.start(), macro_end), new_contents); + }, + ) +} - let target = macro_call.syntax().text_range(); - acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", target, |builder| { - builder.replace(macro_range, paste_instead_of_dbg); - }) +fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option { + let contents = get_valid_macrocall_contents(¯o_call, "dbg")?; + let is_leaf = macro_call.syntax().next_sibling().is_none(); + let macro_text_with_brackets = macro_call.token_tree()?.syntax().text(); + let slice_index = if is_leaf || !needs_parentheses_around_macro_contents(contents) { + TextRange::new(TextSize::of('('), macro_text_with_brackets.len() - TextSize::of(')')) + } else { + // leave parenthesis around macro contents to preserve the semantics + TextRange::up_to(macro_text_with_brackets.len()) + }; + Some(macro_text_with_brackets.slice(slice_index).to_string()) } /// Verifies that the given macro_call actually matches the given name -/// and contains proper ending tokens -fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option { +/// and contains proper ending tokens, then returns the contents between the ending tokens +fn get_valid_macrocall_contents( + macro_call: &ast::MacroCall, + macro_name: &str, +) -> Option> { let path = macro_call.path()?; let name_ref = path.segment()?.name_ref()?; // Make sure it is actually a dbg-macro call, dbg followed by ! let excl = path.syntax().next_sibling_or_token()?; - if name_ref.text() != macro_name || excl.kind() != T![!] { return None; } - let node = macro_call.token_tree()?.syntax().clone(); - let first_child = node.first_child_or_token()?; - let last_child = node.last_child_or_token()?; + let mut children_with_tokens = macro_call.token_tree()?.syntax().children_with_tokens(); + let first_child = children_with_tokens.next()?; + let mut contents_between_brackets = children_with_tokens.collect::>(); + let last_child = contents_between_brackets.pop()?; - match (first_child.kind(), last_child.kind()) { - (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true), - _ => Some(false), + if contents_between_brackets.is_empty() { + None + } else { + match (first_child.kind(), last_child.kind()) { + (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => { + Some(contents_between_brackets) + } + _ => None, + } } } +fn needs_parentheses_around_macro_contents(macro_contents: Vec) -> bool { + if macro_contents.len() < 2 { + return false; + } + + let mut macro_contents_kind_not_in_brackets = Vec::with_capacity(macro_contents.len()); + + let mut first_bracket_in_macro = None; + let mut unpaired_brackets_in_contents = Vec::new(); + for element in macro_contents { + match element.kind() { + T!['('] | T!['['] | T!['{'] => { + if let None = first_bracket_in_macro { + first_bracket_in_macro = Some(element.clone()) + } + unpaired_brackets_in_contents.push(element); + } + T![')'] => { + if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['(']) + { + return true; + } + } + T![']'] => { + if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['[']) + { + return true; + } + } + T!['}'] => { + if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['{']) + { + return true; + } + } + other_kind => { + if unpaired_brackets_in_contents.is_empty() { + macro_contents_kind_not_in_brackets.push(other_kind); + } + } + } + } + + !unpaired_brackets_in_contents.is_empty() + || matches!(first_bracket_in_macro, Some(bracket) if bracket.kind() != T!['(']) + || macro_contents_kind_not_in_brackets + .into_iter() + .any(|macro_contents_kind| macro_contents_kind.is_punct()) +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; @@ -156,6 +212,29 @@ fn foo(n: usize) { ); } + #[test] + fn remove_dbg_from_non_leaf_simple_expression() { + check_assist( + remove_dbg, + " +fn main() { + let mut a = 1; + while dbg!<|>(a) < 10000 { + a += 1; + } +} +", + " +fn main() { + let mut a = 1; + while a < 10000 { + a += 1; + } +} +", + ); + } + #[test] fn test_remove_dbg_keep_expression() { check_assist( -- cgit v1.2.3 From b477f99bd9d6ae81001451cc310efe0ac950eb61 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 11 Sep 2020 00:20:13 +0300 Subject: One more test --- crates/assists/src/handlers/remove_dbg.rs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'crates/assists') diff --git a/crates/assists/src/handlers/remove_dbg.rs b/crates/assists/src/handlers/remove_dbg.rs index 9397efd63..0b581dc22 100644 --- a/crates/assists/src/handlers/remove_dbg.rs +++ b/crates/assists/src/handlers/remove_dbg.rs @@ -242,6 +242,8 @@ fn main() { r#"let res = <|>dbg!(a + b).foo();"#, r#"let res = (a + b).foo();"#, ); + + check_assist(remove_dbg, r#"let res = <|>dbg!(2 + 2) * 5"#, r#"let res = (2 + 2) * 5"#); } #[test] -- cgit v1.2.3