From 8a8be062194604360bbb27ee11961b8a72973f44 Mon Sep 17 00:00:00 2001 From: bravomikekilo Date: Fri, 22 Nov 2019 02:51:40 +0800 Subject: initial invert_if --- crates/ra_assists/src/assists/apply_demorgan.rs | 2 +- crates/ra_assists/src/assists/invert_if.rs | 85 +++++++++++++++++++++++++ crates/ra_assists/src/doc_tests/generated.rs | 17 +++++ crates/ra_assists/src/lib.rs | 2 + docs/user/assists.md | 19 ++++++ 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 crates/ra_assists/src/assists/invert_if.rs diff --git a/crates/ra_assists/src/assists/apply_demorgan.rs b/crates/ra_assists/src/assists/apply_demorgan.rs index 068da1774..0d59a0d24 100644 --- a/crates/ra_assists/src/assists/apply_demorgan.rs +++ b/crates/ra_assists/src/assists/apply_demorgan.rs @@ -57,7 +57,7 @@ fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> { } // This function tries to undo unary negation, or inequality -fn undo_negation(node: SyntaxNode) -> Option { +pub(crate) fn undo_negation(node: SyntaxNode) -> Option { match ast::Expr::cast(node)? { ast::Expr::BinExpr(bin) => match bin.op_kind()? { ast::BinOp::NegatedEqualityTest => { diff --git a/crates/ra_assists/src/assists/invert_if.rs b/crates/ra_assists/src/assists/invert_if.rs new file mode 100644 index 000000000..9a53a3d18 --- /dev/null +++ b/crates/ra_assists/src/assists/invert_if.rs @@ -0,0 +1,85 @@ +use hir::db::HirDatabase; +use ra_syntax::ast::{self, AstNode}; +use ra_syntax::{TextRange, TextUnit}; + +use crate::{Assist, AssistCtx, AssistId}; +use super::apply_demorgan::undo_negation; + +// Assist: invert_if +// +// Apply invert_if +// This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}` +// This also works with `!=`. This assist can only be applied with the cursor +// on `if`. +// +// ``` +// fn main() { +// if<|> !y {A} else {B} +// } +// ``` +// -> +// ``` +// fn main() { +// if y {B} else {A} +// } +// ``` + + +pub(crate) fn invert_if(ctx: AssistCtx) -> Option { + let expr = ctx.find_node_at_offset::()?; + let expr_range = expr.syntax().text_range(); + let if_range = TextRange::offset_len(expr_range.start(), TextUnit::from_usize(2)); + let cursor_in_range = ctx.frange.range.is_subrange(&if_range); + if !cursor_in_range { + return None; + } + + let cond = expr.condition()?.expr()?.syntax().clone(); + let then_node = expr.then_branch()?.syntax().clone(); + + if let ast::ElseBranch::Block(else_block) = expr.else_branch()? { + let flip_cond = undo_negation(cond.clone())?; + let cond_range = cond.text_range(); + let else_node = else_block.syntax(); + let else_range = else_node.text_range(); + let then_range = then_node.text_range(); + ctx.add_assist(AssistId("invert_if"), "invert if branches", |edit| { + edit.target(if_range); + edit.replace(cond_range, flip_cond); + edit.replace(else_range, then_node.text()); + edit.replace(then_range, else_node.text()); + }) + + } else { + None + } + + +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::helpers::{check_assist, check_assist_not_applicable}; + + #[test] + fn invert_if_remove_inequality() { + check_assist(invert_if, "fn f() { i<|>f x != 3 {1} else {3 + 2} }", "fn f() { i<|>f x == 3 {3 + 2} else {1} }") + } + + #[test] + fn invert_if_remove_not() { + check_assist(invert_if, "fn f() { <|>if !cond {3 * 2} else {1} }", "fn f() { <|>if cond {1} else {3 * 2} }") + } + + #[test] + fn invert_if_doesnt_apply_with_cursor_not_on_if() { + check_assist_not_applicable(invert_if, "fn f() { if !<|>cond {3 * 2} else {1} }") + } + + #[test] + fn invert_if_doesnt_apply_without_negated() { + check_assist_not_applicable(invert_if, "fn f() { i<|>f cond {3 * 2} else {1} }") + } +} diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs index 176761efb..1ccc016d3 100644 --- a/crates/ra_assists/src/doc_tests/generated.rs +++ b/crates/ra_assists/src/doc_tests/generated.rs @@ -341,6 +341,23 @@ fn main() { ) } +#[test] +fn doctest_invert_if() { + check( + "invert_if", + r#####" +fn main() { + if<|> !y {A} else {B} +} +"#####, + r#####" +fn main() { + if y {B} else {A} +} +"#####, + ) +} + #[test] fn doctest_make_raw_string() { check( diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index f2f0dacbf..a372bd8b9 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -97,6 +97,7 @@ mod assists { mod add_impl; mod add_new; mod apply_demorgan; + mod invert_if; mod flip_comma; mod flip_binexpr; mod flip_trait_bound; @@ -122,6 +123,7 @@ mod assists { add_impl::add_impl, add_new::add_new, apply_demorgan::apply_demorgan, + invert_if::invert_if, change_visibility::change_visibility, fill_match_arms::fill_match_arms, merge_match_arms::merge_match_arms, diff --git a/docs/user/assists.md b/docs/user/assists.md index 8da7578e2..6e7811bd6 100644 --- a/docs/user/assists.md +++ b/docs/user/assists.md @@ -329,6 +329,25 @@ fn main() { } ``` +## `invert_if` + +Apply invert_if +This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}` +This also works with `!=`. This assist can only be applied with the cursor +on `if`. + +```rust +// BEFORE +fn main() { + if┃ !y {A} else {B} +} + +// AFTER +fn main() { + if y {B} else {A} +} +``` + ## `make_raw_string` Adds `r#` to a plain string literal. -- cgit v1.2.3 From 1ebfa908d50a7ef4765d2abb432531d9c98cbb58 Mon Sep 17 00:00:00 2001 From: bravomikekilo Date: Fri, 22 Nov 2019 03:18:22 +0800 Subject: fix tidy test --- crates/ra_assists/src/assists/invert_if.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/ra_assists/src/assists/invert_if.rs b/crates/ra_assists/src/assists/invert_if.rs index 9a53a3d18..c2c8529fe 100644 --- a/crates/ra_assists/src/assists/invert_if.rs +++ b/crates/ra_assists/src/assists/invert_if.rs @@ -2,8 +2,8 @@ use hir::db::HirDatabase; use ra_syntax::ast::{self, AstNode}; use ra_syntax::{TextRange, TextUnit}; -use crate::{Assist, AssistCtx, AssistId}; use super::apply_demorgan::undo_negation; +use crate::{Assist, AssistCtx, AssistId}; // Assist: invert_if // @@ -24,7 +24,6 @@ use super::apply_demorgan::undo_negation; // } // ``` - pub(crate) fn invert_if(ctx: AssistCtx) -> Option { let expr = ctx.find_node_at_offset::()?; let expr_range = expr.syntax().text_range(); @@ -49,12 +48,9 @@ pub(crate) fn invert_if(ctx: AssistCtx) -> Option { edit.replace(else_range, then_node.text()); edit.replace(then_range, else_node.text()); }) - } else { None } - - } #[cfg(test)] @@ -65,12 +61,20 @@ mod tests { #[test] fn invert_if_remove_inequality() { - check_assist(invert_if, "fn f() { i<|>f x != 3 {1} else {3 + 2} }", "fn f() { i<|>f x == 3 {3 + 2} else {1} }") + check_assist( + invert_if, + "fn f() { i<|>f x != 3 {1} else {3 + 2} }", + "fn f() { i<|>f x == 3 {3 + 2} else {1} }", + ) } #[test] fn invert_if_remove_not() { - check_assist(invert_if, "fn f() { <|>if !cond {3 * 2} else {1} }", "fn f() { <|>if cond {1} else {3 * 2} }") + check_assist( + invert_if, + "fn f() { <|>if !cond {3 * 2} else {1} }", + "fn f() { <|>if cond {1} else {3 * 2} }", + ) } #[test] -- cgit v1.2.3 From adac4fc2f21117486356063d82d79f8c3add084a Mon Sep 17 00:00:00 2001 From: bravomikekilo Date: Sun, 24 Nov 2019 13:14:57 +0800 Subject: do refact and fix some issue --- crates/ra_assists/src/assists/apply_demorgan.rs | 40 ++++-------------- crates/ra_assists/src/assists/invert_if.rs | 55 +++++++++++++++---------- crates/ra_assists/src/doc_tests/generated.rs | 4 +- crates/ra_syntax/src/ast/edit.rs | 12 +++++- crates/ra_syntax/src/ast/expr_extensions.rs | 2 +- crates/ra_syntax/src/ast/make.rs | 15 ++++++- docs/user/assists.md | 4 +- 7 files changed, 72 insertions(+), 60 deletions(-) diff --git a/crates/ra_assists/src/assists/apply_demorgan.rs b/crates/ra_assists/src/assists/apply_demorgan.rs index 0d59a0d24..7c57c0560 100644 --- a/crates/ra_assists/src/assists/apply_demorgan.rs +++ b/crates/ra_assists/src/assists/apply_demorgan.rs @@ -1,6 +1,6 @@ +use super::invert_if::invert_boolean_expression; use hir::db::HirDatabase; use ra_syntax::ast::{self, AstNode}; -use ra_syntax::SyntaxNode; use crate::{Assist, AssistCtx, AssistId}; @@ -32,18 +32,18 @@ pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option if !cursor_in_range { return None; } - let lhs = expr.lhs()?.syntax().clone(); - let lhs_range = lhs.text_range(); - let rhs = expr.rhs()?.syntax().clone(); - let rhs_range = rhs.text_range(); - let not_lhs = undo_negation(lhs)?; - let not_rhs = undo_negation(rhs)?; + let lhs = expr.lhs()?; + let lhs_range = lhs.syntax().text_range(); + let rhs = expr.rhs()?; + let rhs_range = rhs.syntax().text_range(); + let not_lhs = invert_boolean_expression(&lhs)?; + let not_rhs = invert_boolean_expression(&rhs)?; ctx.add_assist(AssistId("apply_demorgan"), "apply demorgan's law", |edit| { edit.target(op_range); edit.replace(op_range, opposite_op); - edit.replace(lhs_range, format!("!({}", not_lhs)); - edit.replace(rhs_range, format!("{})", not_rhs)); + edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); + edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); }) } @@ -56,28 +56,6 @@ fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> { } } -// This function tries to undo unary negation, or inequality -pub(crate) fn undo_negation(node: SyntaxNode) -> Option { - match ast::Expr::cast(node)? { - ast::Expr::BinExpr(bin) => match bin.op_kind()? { - ast::BinOp::NegatedEqualityTest => { - let lhs = bin.lhs()?.syntax().text(); - let rhs = bin.rhs()?.syntax().text(); - Some(format!("{} == {}", lhs, rhs)) - } - _ => None, - }, - ast::Expr::PrefixExpr(pe) => match pe.op_kind()? { - ast::PrefixOp::Not => { - let child = pe.expr()?.syntax().text(); - Some(String::from(child)) - } - _ => None, - }, - _ => None, - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/ra_assists/src/assists/invert_if.rs b/crates/ra_assists/src/assists/invert_if.rs index c2c8529fe..bababa3e2 100644 --- a/crates/ra_assists/src/assists/invert_if.rs +++ b/crates/ra_assists/src/assists/invert_if.rs @@ -1,8 +1,7 @@ use hir::db::HirDatabase; use ra_syntax::ast::{self, AstNode}; -use ra_syntax::{TextRange, TextUnit}; +use ra_syntax::T; -use super::apply_demorgan::undo_negation; use crate::{Assist, AssistCtx, AssistId}; // Assist: invert_if @@ -14,42 +13,56 @@ use crate::{Assist, AssistCtx, AssistId}; // // ``` // fn main() { -// if<|> !y {A} else {B} +// if<|> !y { A } else { B } // } // ``` // -> // ``` // fn main() { -// if y {B} else {A} +// if y { B } else { A } // } // ``` pub(crate) fn invert_if(ctx: AssistCtx) -> Option { - let expr = ctx.find_node_at_offset::()?; - let expr_range = expr.syntax().text_range(); - let if_range = TextRange::offset_len(expr_range.start(), TextUnit::from_usize(2)); + let if_keyword = ctx.find_token_at_offset(T![if])?; + let expr = ast::IfExpr::cast(if_keyword.parent())?; + let if_range = if_keyword.text_range(); let cursor_in_range = ctx.frange.range.is_subrange(&if_range); if !cursor_in_range { return None; } - let cond = expr.condition()?.expr()?.syntax().clone(); + let cond = expr.condition()?.expr()?; let then_node = expr.then_branch()?.syntax().clone(); if let ast::ElseBranch::Block(else_block) = expr.else_branch()? { - let flip_cond = undo_negation(cond.clone())?; - let cond_range = cond.text_range(); + let flip_cond = invert_boolean_expression(&cond)?; + let cond_range = cond.syntax().text_range(); let else_node = else_block.syntax(); let else_range = else_node.text_range(); let then_range = then_node.text_range(); - ctx.add_assist(AssistId("invert_if"), "invert if branches", |edit| { + return ctx.add_assist(AssistId("invert_if"), "invert if branches", |edit| { edit.target(if_range); - edit.replace(cond_range, flip_cond); + edit.replace(cond_range, flip_cond.syntax().text()); edit.replace(else_range, then_node.text()); edit.replace(then_range, else_node.text()); - }) - } else { - None + }); + } + + None +} + +pub(crate) fn invert_boolean_expression(expr: &ast::Expr) -> Option { + match expr { + ast::Expr::BinExpr(bin) => match bin.op_kind()? { + ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()), + _ => None, + }, + ast::Expr::PrefixExpr(pe) => match pe.op_kind()? { + ast::PrefixOp::Not => pe.expr(), + _ => None, + }, + _ => None, } } @@ -63,8 +76,8 @@ mod tests { fn invert_if_remove_inequality() { check_assist( invert_if, - "fn f() { i<|>f x != 3 {1} else {3 + 2} }", - "fn f() { i<|>f x == 3 {3 + 2} else {1} }", + "fn f() { i<|>f x != 3 { 1 } else { 3 + 2 } }", + "fn f() { i<|>f x == 3 { 3 + 2 } else { 1 } }", ) } @@ -72,18 +85,18 @@ mod tests { fn invert_if_remove_not() { check_assist( invert_if, - "fn f() { <|>if !cond {3 * 2} else {1} }", - "fn f() { <|>if cond {1} else {3 * 2} }", + "fn f() { <|>if !cond { 3 * 2 } else { 1 } }", + "fn f() { <|>if cond { 1 } else { 3 * 2 } }", ) } #[test] fn invert_if_doesnt_apply_with_cursor_not_on_if() { - check_assist_not_applicable(invert_if, "fn f() { if !<|>cond {3 * 2} else {1} }") + check_assist_not_applicable(invert_if, "fn f() { if !<|>cond { 3 * 2 } else { 1 } }") } #[test] fn invert_if_doesnt_apply_without_negated() { - check_assist_not_applicable(invert_if, "fn f() { i<|>f cond {3 * 2} else {1} }") + check_assist_not_applicable(invert_if, "fn f() { i<|>f cond { 3 * 2 } else { 1 } }") } } diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs index 1ccc016d3..3c716c2d1 100644 --- a/crates/ra_assists/src/doc_tests/generated.rs +++ b/crates/ra_assists/src/doc_tests/generated.rs @@ -347,12 +347,12 @@ fn doctest_invert_if() { "invert_if", r#####" fn main() { - if<|> !y {A} else {B} + if<|> !y { A } else { B } } "#####, r#####" fn main() { - if y {B} else {A} + if y { B } else { A } } "#####, ) diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 6f005a2d8..95bf9db14 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs @@ -13,11 +13,21 @@ use crate::{ make::{self, tokens}, AstNode, TypeBoundsOwner, }, - AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, + AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, SyntaxKind, SyntaxKind::{ATTR, COMMENT, WHITESPACE}, SyntaxNode, SyntaxToken, T, }; +impl ast::BinExpr { + #[must_use] + pub fn replace_op(&self, op: SyntaxKind) -> Option { + let op_node: SyntaxElement = self.op_details()?.0.into(); + let to_insert: Option = Some(tokens::op(op).into()); + let replace_range = RangeInclusive::new(op_node.clone(), op_node); + Some(replace_children(self, replace_range, to_insert.into_iter())) + } +} + impl ast::FnDef { #[must_use] pub fn with_body(&self, body: ast::Block) -> ast::FnDef { diff --git a/crates/ra_syntax/src/ast/expr_extensions.rs b/crates/ra_syntax/src/ast/expr_extensions.rs index 7c53aa934..2fd039837 100644 --- a/crates/ra_syntax/src/ast/expr_extensions.rs +++ b/crates/ra_syntax/src/ast/expr_extensions.rs @@ -127,7 +127,7 @@ pub enum BinOp { } impl ast::BinExpr { - fn op_details(&self) -> Option<(SyntaxToken, BinOp)> { + pub fn op_details(&self) -> Option<(SyntaxToken, BinOp)> { self.syntax().children_with_tokens().filter_map(|it| it.into_token()).find_map(|c| { let bin_op = match c.kind() { T![||] => BinOp::BooleanOr, diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index 9749327fa..40db570da 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs @@ -173,10 +173,21 @@ fn ast_from_text(text: &str) -> N { } pub mod tokens { - use crate::{AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken, T}; + use crate::{AstNode, Parse, SourceFile, SyntaxKind, SyntaxKind::*, SyntaxToken, T}; use once_cell::sync::Lazy; - static SOURCE_FILE: Lazy> = Lazy::new(|| SourceFile::parse(",\n; ;")); + static SOURCE_FILE: Lazy> = + Lazy::new(|| SourceFile::parse("const C: () = (1 != 1, 2 == 2)\n;")); + + pub fn op(op: SyntaxKind) -> SyntaxToken { + SOURCE_FILE + .tree() + .syntax() + .descendants_with_tokens() + .filter_map(|it| it.into_token()) + .find(|it| it.kind() == op) + .unwrap() + } pub fn comma() -> SyntaxToken { SOURCE_FILE diff --git a/docs/user/assists.md b/docs/user/assists.md index 6e7811bd6..6f4c30bee 100644 --- a/docs/user/assists.md +++ b/docs/user/assists.md @@ -339,12 +339,12 @@ on `if`. ```rust // BEFORE fn main() { - if┃ !y {A} else {B} + if┃ !y { A } else { B } } // AFTER fn main() { - if y {B} else {A} + if y { B } else { A } } ``` -- cgit v1.2.3