Use better heuristics for replacement text when removing dbg!
This commit is contained in:
parent
5c336e266f
commit
208d4609b0
@ -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::<ast::MacroCall>()?;
|
||||
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();
|
||||
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);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// 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()
|
||||
fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> {
|
||||
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())
|
||||
};
|
||||
|
||||
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);
|
||||
})
|
||||
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<bool> {
|
||||
/// 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<Vec<SyntaxElement>> {
|
||||
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::<Vec<_>>();
|
||||
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<SyntaxElement>) -> 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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user