From 5f79279b487fad2a74d21588c49af83a79d5fdec Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 6 Dec 2022 16:18:25 +0000 Subject: [PATCH 1/5] Add `remove_parentheses` assist --- .../src/handlers/remove_parentheses.rs | 86 +++++++++++++++++++ crates/ide-assists/src/lib.rs | 2 + crates/ide-assists/src/tests/generated.rs | 17 ++++ 3 files changed, 105 insertions(+) create mode 100644 crates/ide-assists/src/handlers/remove_parentheses.rs diff --git a/crates/ide-assists/src/handlers/remove_parentheses.rs b/crates/ide-assists/src/handlers/remove_parentheses.rs new file mode 100644 index 0000000000..9a1f9268d0 --- /dev/null +++ b/crates/ide-assists/src/handlers/remove_parentheses.rs @@ -0,0 +1,86 @@ +use syntax::{ast, AstNode, SyntaxKind, TextRange}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: remove_parentheses +// +// Removes useless parentheses. +// +// ``` +// fn main() { +// _ = $0(2) + 2; +// } +// ``` +// -> +// ``` +// fn main() { +// _ = 2 + 2; +// } +// ``` +pub(crate) fn remove_parentheses(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let parens = ctx.find_node_at_offset::()?; + let l_paren = parens.l_paren_token()?; + let r_paren = parens.r_paren_token()?; + + let cursor_in_range = l_paren.text_range().contains_range(ctx.selection_trimmed()) + || r_paren.text_range().contains_range(ctx.selection_trimmed()); + if !cursor_in_range { + return None; + } + + // FIXME: check that precedence is right + + let delete_from_l = l_paren.text_range().start(); + let delete_to_l = match l_paren.next_token() { + Some(it) if it.kind() == SyntaxKind::WHITESPACE => it.text_range().end(), + _ => l_paren.text_range().end(), + }; + + let delete_from_r = match r_paren.prev_token() { + Some(it) if it.kind() == SyntaxKind::WHITESPACE => it.text_range().start(), + _ => r_paren.text_range().start(), + }; + let delete_to_r = r_paren.text_range().end(); + + let target = parens.syntax().text_range(); + acc.add( + AssistId("remove_parentheses", AssistKind::Refactor), + "Remove parentheses", + target, + |builder| { + builder.delete(TextRange::new(delete_from_l, delete_to_l)); + builder.delete(TextRange::new(delete_from_r, delete_to_r)); + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn remove_parens_simple() { + check_assist(remove_parentheses, r#"fn f() { $0(2) + 2; }"#, r#"fn f() { 2 + 2; }"#); + check_assist(remove_parentheses, r#"fn f() { ($02) + 2; }"#, r#"fn f() { 2 + 2; }"#); + check_assist(remove_parentheses, r#"fn f() { (2)$0 + 2; }"#, r#"fn f() { 2 + 2; }"#); + check_assist(remove_parentheses, r#"fn f() { (2$0) + 2; }"#, r#"fn f() { 2 + 2; }"#); + } + + // We should not permit assist here and yet + #[test] + fn remove_parens_wrong() { + check_assist( + remove_parentheses, + r#"fn f() { $0(2 + 2) * 8; }"#, + r#"fn f() { 2 + 2 * 8; }"#, + ); + } + + #[test] + fn remove_parens_doesnt_apply_with_cursor_not_on_paren() { + check_assist_not_applicable(remove_parentheses, r#"fn f() { (2 +$0 2) }"#); + check_assist_not_applicable(remove_parentheses, r#"fn f() {$0 (2 + 2) }"#); + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index a55de800b3..8b1247c640 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -179,6 +179,7 @@ mod handlers { mod remove_dbg; mod remove_mut; mod remove_unused_param; + mod remove_parentheses; mod reorder_fields; mod reorder_impl_items; mod replace_try_expr_with_match; @@ -280,6 +281,7 @@ mod handlers { remove_dbg::remove_dbg, remove_mut::remove_mut, remove_unused_param::remove_unused_param, + remove_parentheses::remove_parentheses, reorder_fields::reorder_fields, reorder_impl_items::reorder_impl_items, replace_try_expr_with_match::replace_try_expr_with_match, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index ccd38119c4..80b8c27c7c 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1978,6 +1978,23 @@ impl Walrus { ) } +#[test] +fn doctest_remove_parentheses() { + check_doc_test( + "remove_parentheses", + r#####" +fn main() { + _ = $0(2) + 2; +} +"#####, + r#####" +fn main() { + _ = 2 + 2; +} +"#####, + ) +} + #[test] fn doctest_remove_unused_param() { check_doc_test( From ab061945a1c3e4602b6cf42a2f36ea139fe0afe5 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 6 Dec 2022 19:11:24 +0000 Subject: [PATCH 2/5] Consider expression precedense in `remove_parentheses` assist --- .../src/handlers/remove_parentheses.rs | 122 +++++++++++++++++- 1 file changed, 117 insertions(+), 5 deletions(-) diff --git a/crates/ide-assists/src/handlers/remove_parentheses.rs b/crates/ide-assists/src/handlers/remove_parentheses.rs index 9a1f9268d0..ff500ba035 100644 --- a/crates/ide-assists/src/handlers/remove_parentheses.rs +++ b/crates/ide-assists/src/handlers/remove_parentheses.rs @@ -28,7 +28,13 @@ pub(crate) fn remove_parentheses(acc: &mut Assists, ctx: &AssistContext<'_>) -> return None; } - // FIXME: check that precedence is right + let expr = parens.expr()?; + let parent = ast::Expr::cast(parens.syntax().parent()?); + let is_ok_to_remove = + parent.map_or(true, |p| ExprPrecedence::of(&expr) >= ExprPrecedence::of(&p)); + if !is_ok_to_remove { + return None; + } let delete_from_l = l_paren.text_range().start(); let delete_to_l = match l_paren.next_token() { @@ -54,6 +60,97 @@ pub(crate) fn remove_parentheses(acc: &mut Assists, ctx: &AssistContext<'_>) -> ) } +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub enum ExprPrecedence { + // N.B.: Order is important + /// Precedence is unknown + Dummy, + Closure, + Jump, + Range, + Bin(BinOpPresedence), + Prefix, + Postfix, + Paren, +} + +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub enum BinOpPresedence { + // N.B.: Order is important + /// `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `|=`, `&=` + Assign, + /// `||` + LOr, + /// `&&` + LAnd, + /// `<`, `<=`, `>`, `>=`, `==` and `!=` + Cmp, + /// `|` + BitOr, + /// `^` + BitXor, + /// `&` + BitAnd, + /// `<<` and `>>` + Shift, + /// `+` and `-` + Add, + /// `*`, `/` and `%` + Mul, + /// `as` + As, +} + +impl ExprPrecedence { + pub fn of(expr: &ast::Expr) -> Self { + // Copied from + use ast::Expr::*; + + match expr { + ClosureExpr(_) => Self::Closure, + + ContinueExpr(_) | ReturnExpr(_) | YieldExpr(_) | BreakExpr(_) => Self::Jump, + + RangeExpr(_) => Self::Range, + + BinExpr(bin_expr) => bin_expr + .op_kind() + .map(|op| match op { + ast::BinaryOp::LogicOp(op) => match op { + ast::LogicOp::And => BinOpPresedence::LAnd, + ast::LogicOp::Or => BinOpPresedence::LOr, + }, + ast::BinaryOp::ArithOp(op) => match op { + ast::ArithOp::Add => BinOpPresedence::Add, + ast::ArithOp::Mul => BinOpPresedence::Mul, + ast::ArithOp::Sub => BinOpPresedence::Add, + ast::ArithOp::Div => BinOpPresedence::Mul, + ast::ArithOp::Rem => BinOpPresedence::Mul, + ast::ArithOp::Shl => BinOpPresedence::Shift, + ast::ArithOp::Shr => BinOpPresedence::Shift, + ast::ArithOp::BitXor => BinOpPresedence::BitXor, + ast::ArithOp::BitOr => BinOpPresedence::BitOr, + ast::ArithOp::BitAnd => BinOpPresedence::BitAnd, + }, + ast::BinaryOp::CmpOp(_) => BinOpPresedence::Cmp, + ast::BinaryOp::Assignment { .. } => BinOpPresedence::Assign, + }) + .map(Self::Bin) + .unwrap_or(Self::Dummy), + CastExpr(_) => Self::Bin(BinOpPresedence::As), + + BoxExpr(_) | RefExpr(_) | LetExpr(_) | PrefixExpr(_) => Self::Prefix, + + AwaitExpr(_) | CallExpr(_) | MethodCallExpr(_) | FieldExpr(_) | IndexExpr(_) + | TryExpr(_) | MacroExpr(_) => Self::Postfix, + + ArrayExpr(_) | TupleExpr(_) | Literal(_) | PathExpr(_) | ParenExpr(_) | IfExpr(_) + | WhileExpr(_) | ForExpr(_) | LoopExpr(_) | MatchExpr(_) | BlockExpr(_) + | RecordExpr(_) | UnderscoreExpr(_) => Self::Paren, + } + } +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; @@ -68,14 +165,29 @@ mod tests { check_assist(remove_parentheses, r#"fn f() { (2$0) + 2; }"#, r#"fn f() { 2 + 2; }"#); } - // We should not permit assist here and yet #[test] - fn remove_parens_wrong() { + fn remove_parens_precedence() { check_assist( remove_parentheses, - r#"fn f() { $0(2 + 2) * 8; }"#, - r#"fn f() { 2 + 2 * 8; }"#, + r#"fn f() { $0(2 * 3) + 1; }"#, + r#"fn f() { 2 * 3 + 1; }"#, ); + check_assist(remove_parentheses, r#"fn f() { ( $0(2) ); }"#, r#"fn f() { ( 2 ); }"#); + check_assist(remove_parentheses, r#"fn f() { $0(2?)?; }"#, r#"fn f() { 2??; }"#); + check_assist(remove_parentheses, r#"fn f() { f(($02 + 2)); }"#, r#"fn f() { f(2 + 2); }"#); + check_assist( + remove_parentheses, + r#"fn f() { (1<2)&&$0(3>4); }"#, + r#"fn f() { (1<2)&&3>4; }"#, + ); + } + + #[test] + fn remove_parens_doesnt_apply_precedence() { + check_assist_not_applicable(remove_parentheses, r#"fn f() { $0(2 + 2) * 8; }"#); + check_assist_not_applicable(remove_parentheses, r#"fn f() { $0(2 + 2).f(); }"#); + check_assist_not_applicable(remove_parentheses, r#"fn f() { $0(2 + 2).await; }"#); + check_assist_not_applicable(remove_parentheses, r#"fn f() { $0!(2..2); }"#); } #[test] From 2870b01ec0d434261b14726c2b2671001b817772 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 8 Dec 2022 18:22:57 +0000 Subject: [PATCH 3/5] Explicitly say that the assist removes *redundant* parentheses --- crates/ide-assists/src/handlers/remove_parentheses.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ide-assists/src/handlers/remove_parentheses.rs b/crates/ide-assists/src/handlers/remove_parentheses.rs index ff500ba035..de79962dd8 100644 --- a/crates/ide-assists/src/handlers/remove_parentheses.rs +++ b/crates/ide-assists/src/handlers/remove_parentheses.rs @@ -4,7 +4,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; // Assist: remove_parentheses // -// Removes useless parentheses. +// Removes redundant parentheses. // // ``` // fn main() { @@ -51,7 +51,7 @@ pub(crate) fn remove_parentheses(acc: &mut Assists, ctx: &AssistContext<'_>) -> let target = parens.syntax().text_range(); acc.add( AssistId("remove_parentheses", AssistKind::Refactor), - "Remove parentheses", + "Remove redundant parentheses", target, |builder| { builder.delete(TextRange::new(delete_from_l, delete_to_l)); From 8d42439a7d5c3edbb0ef6d1398436944ed662111 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 8 Dec 2022 18:44:03 +0000 Subject: [PATCH 4/5] Move precedence handling to `crates/syntax` --- .../src/handlers/remove_parentheses.rs | 94 +------------- crates/syntax/src/ast.rs | 1 + crates/syntax/src/ast/prec.rs | 115 ++++++++++++++++++ 3 files changed, 117 insertions(+), 93 deletions(-) create mode 100644 crates/syntax/src/ast/prec.rs diff --git a/crates/ide-assists/src/handlers/remove_parentheses.rs b/crates/ide-assists/src/handlers/remove_parentheses.rs index de79962dd8..30e9609f7a 100644 --- a/crates/ide-assists/src/handlers/remove_parentheses.rs +++ b/crates/ide-assists/src/handlers/remove_parentheses.rs @@ -30,8 +30,7 @@ pub(crate) fn remove_parentheses(acc: &mut Assists, ctx: &AssistContext<'_>) -> let expr = parens.expr()?; let parent = ast::Expr::cast(parens.syntax().parent()?); - let is_ok_to_remove = - parent.map_or(true, |p| ExprPrecedence::of(&expr) >= ExprPrecedence::of(&p)); + let is_ok_to_remove = expr.precedence() >= parent.as_ref().and_then(ast::Expr::precedence); if !is_ok_to_remove { return None; } @@ -60,97 +59,6 @@ pub(crate) fn remove_parentheses(acc: &mut Assists, ctx: &AssistContext<'_>) -> ) } -#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] -pub enum ExprPrecedence { - // N.B.: Order is important - /// Precedence is unknown - Dummy, - Closure, - Jump, - Range, - Bin(BinOpPresedence), - Prefix, - Postfix, - Paren, -} - -#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] -pub enum BinOpPresedence { - // N.B.: Order is important - /// `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `|=`, `&=` - Assign, - /// `||` - LOr, - /// `&&` - LAnd, - /// `<`, `<=`, `>`, `>=`, `==` and `!=` - Cmp, - /// `|` - BitOr, - /// `^` - BitXor, - /// `&` - BitAnd, - /// `<<` and `>>` - Shift, - /// `+` and `-` - Add, - /// `*`, `/` and `%` - Mul, - /// `as` - As, -} - -impl ExprPrecedence { - pub fn of(expr: &ast::Expr) -> Self { - // Copied from - use ast::Expr::*; - - match expr { - ClosureExpr(_) => Self::Closure, - - ContinueExpr(_) | ReturnExpr(_) | YieldExpr(_) | BreakExpr(_) => Self::Jump, - - RangeExpr(_) => Self::Range, - - BinExpr(bin_expr) => bin_expr - .op_kind() - .map(|op| match op { - ast::BinaryOp::LogicOp(op) => match op { - ast::LogicOp::And => BinOpPresedence::LAnd, - ast::LogicOp::Or => BinOpPresedence::LOr, - }, - ast::BinaryOp::ArithOp(op) => match op { - ast::ArithOp::Add => BinOpPresedence::Add, - ast::ArithOp::Mul => BinOpPresedence::Mul, - ast::ArithOp::Sub => BinOpPresedence::Add, - ast::ArithOp::Div => BinOpPresedence::Mul, - ast::ArithOp::Rem => BinOpPresedence::Mul, - ast::ArithOp::Shl => BinOpPresedence::Shift, - ast::ArithOp::Shr => BinOpPresedence::Shift, - ast::ArithOp::BitXor => BinOpPresedence::BitXor, - ast::ArithOp::BitOr => BinOpPresedence::BitOr, - ast::ArithOp::BitAnd => BinOpPresedence::BitAnd, - }, - ast::BinaryOp::CmpOp(_) => BinOpPresedence::Cmp, - ast::BinaryOp::Assignment { .. } => BinOpPresedence::Assign, - }) - .map(Self::Bin) - .unwrap_or(Self::Dummy), - CastExpr(_) => Self::Bin(BinOpPresedence::As), - - BoxExpr(_) | RefExpr(_) | LetExpr(_) | PrefixExpr(_) => Self::Prefix, - - AwaitExpr(_) | CallExpr(_) | MethodCallExpr(_) | FieldExpr(_) | IndexExpr(_) - | TryExpr(_) | MacroExpr(_) => Self::Postfix, - - ArrayExpr(_) | TupleExpr(_) | Literal(_) | PathExpr(_) | ParenExpr(_) | IfExpr(_) - | WhileExpr(_) | ForExpr(_) | LoopExpr(_) | MatchExpr(_) | BlockExpr(_) - | RecordExpr(_) | UnderscoreExpr(_) => Self::Paren, - } - } -} - #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs index 4aa64d0d6e..10c0457583 100644 --- a/crates/syntax/src/ast.rs +++ b/crates/syntax/src/ast.rs @@ -9,6 +9,7 @@ mod operators; pub mod edit; pub mod edit_in_place; pub mod make; +pub mod prec; use std::marker::PhantomData; diff --git a/crates/syntax/src/ast/prec.rs b/crates/syntax/src/ast/prec.rs new file mode 100644 index 0000000000..6253c4dc3e --- /dev/null +++ b/crates/syntax/src/ast/prec.rs @@ -0,0 +1,115 @@ +//! Precedence representation. + +use crate::ast::{self, BinExpr, Expr}; + +/// Precedence of an expression. +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub enum ExprPrecedence { + // N.B.: Order is important + Closure, + Jump, + Range, + Bin(BinOpPresedence), + Prefix, + Postfix, + Paren, +} + +/// Precedence of a binary operator. +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub enum BinOpPresedence { + // N.B.: Order is important + /// `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `|=`, `&=` + Assign, + /// `||` + LOr, + /// `&&` + LAnd, + /// `<`, `<=`, `>`, `>=`, `==` and `!=` + Cmp, + /// `|` + BitOr, + /// `^` + BitXor, + /// `&` + BitAnd, + /// `<<` and `>>` + Shift, + /// `+` and `-` + Add, + /// `*`, `/` and `%` + Mul, + /// `as` + As, +} + +impl Expr { + /// Returns precedence of this expression. + /// Usefull to preserve semantics in assists. + /// + /// Returns `None` if this is a [`BinExpr`] and its [`op_kind`] returns `None`. + /// + /// [`op_kind`]: BinExpr::op_kind + /// [`BinExpr`]: Expr::BinExpr + pub fn precedence(&self) -> Option { + // Copied from + use Expr::*; + + let prec = match self { + ClosureExpr(_) => ExprPrecedence::Closure, + + ContinueExpr(_) | ReturnExpr(_) | YieldExpr(_) | BreakExpr(_) => ExprPrecedence::Jump, + + RangeExpr(_) => ExprPrecedence::Range, + + BinExpr(bin_expr) => return bin_expr.precedence().map(ExprPrecedence::Bin), + CastExpr(_) => ExprPrecedence::Bin(BinOpPresedence::As), + + BoxExpr(_) | RefExpr(_) | LetExpr(_) | PrefixExpr(_) => ExprPrecedence::Prefix, + + AwaitExpr(_) | CallExpr(_) | MethodCallExpr(_) | FieldExpr(_) | IndexExpr(_) + | TryExpr(_) | MacroExpr(_) => ExprPrecedence::Postfix, + + ArrayExpr(_) | TupleExpr(_) | Literal(_) | PathExpr(_) | ParenExpr(_) | IfExpr(_) + | WhileExpr(_) | ForExpr(_) | LoopExpr(_) | MatchExpr(_) | BlockExpr(_) + | RecordExpr(_) | UnderscoreExpr(_) => ExprPrecedence::Paren, + }; + + Some(prec) + } +} + +impl BinExpr { + /// Returns precedence of this binary expression. + /// Usefull to preserve semantics in assists. + /// + /// Returns `None` if [`op_kind`] returns `None`. + /// + /// [`op_kind`]: BinExpr::op_kind + pub fn precedence(&self) -> Option { + use ast::{ArithOp::*, BinaryOp::*, LogicOp::*}; + + let prec = match self.op_kind()? { + LogicOp(op) => match op { + And => BinOpPresedence::LAnd, + Or => BinOpPresedence::LOr, + }, + ArithOp(op) => match op { + Add => BinOpPresedence::Add, + Mul => BinOpPresedence::Mul, + Sub => BinOpPresedence::Add, + Div => BinOpPresedence::Mul, + Rem => BinOpPresedence::Mul, + Shl => BinOpPresedence::Shift, + Shr => BinOpPresedence::Shift, + BitXor => BinOpPresedence::BitXor, + BitOr => BinOpPresedence::BitOr, + BitAnd => BinOpPresedence::BitAnd, + }, + CmpOp(_) => BinOpPresedence::Cmp, + Assignment { .. } => BinOpPresedence::Assign, + }; + + Some(prec) + } +} From ba6f0befc8b406120eb0d927c902986e997fb841 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 8 Dec 2022 18:54:08 +0000 Subject: [PATCH 5/5] Simplify `remove_parentheses`'s implementation --- .../src/handlers/remove_parentheses.rs | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/crates/ide-assists/src/handlers/remove_parentheses.rs b/crates/ide-assists/src/handlers/remove_parentheses.rs index 30e9609f7a..185beda9d0 100644 --- a/crates/ide-assists/src/handlers/remove_parentheses.rs +++ b/crates/ide-assists/src/handlers/remove_parentheses.rs @@ -1,4 +1,4 @@ -use syntax::{ast, AstNode, SyntaxKind, TextRange}; +use syntax::{ast, AstNode}; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -19,43 +19,28 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; // ``` pub(crate) fn remove_parentheses(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let parens = ctx.find_node_at_offset::()?; - let l_paren = parens.l_paren_token()?; - let r_paren = parens.r_paren_token()?; - let cursor_in_range = l_paren.text_range().contains_range(ctx.selection_trimmed()) - || r_paren.text_range().contains_range(ctx.selection_trimmed()); + let cursor_in_range = + parens.l_paren_token()?.text_range().contains_range(ctx.selection_trimmed()) + || parens.r_paren_token()?.text_range().contains_range(ctx.selection_trimmed()); if !cursor_in_range { return None; } let expr = parens.expr()?; + let parent = ast::Expr::cast(parens.syntax().parent()?); let is_ok_to_remove = expr.precedence() >= parent.as_ref().and_then(ast::Expr::precedence); if !is_ok_to_remove { return None; } - let delete_from_l = l_paren.text_range().start(); - let delete_to_l = match l_paren.next_token() { - Some(it) if it.kind() == SyntaxKind::WHITESPACE => it.text_range().end(), - _ => l_paren.text_range().end(), - }; - - let delete_from_r = match r_paren.prev_token() { - Some(it) if it.kind() == SyntaxKind::WHITESPACE => it.text_range().start(), - _ => r_paren.text_range().start(), - }; - let delete_to_r = r_paren.text_range().end(); - let target = parens.syntax().text_range(); acc.add( AssistId("remove_parentheses", AssistKind::Refactor), "Remove redundant parentheses", target, - |builder| { - builder.delete(TextRange::new(delete_from_l, delete_to_l)); - builder.delete(TextRange::new(delete_from_r, delete_to_r)); - }, + |builder| builder.replace_ast(parens.into(), expr), ) }