diff options
-rw-r--r-- | crates/ra_assists/src/assists/apply_demorgan.rs | 40 | ||||
-rw-r--r-- | crates/ra_assists/src/assists/invert_if.rs | 102 | ||||
-rw-r--r-- | crates/ra_assists/src/doc_tests/generated.rs | 17 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/edit.rs | 12 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/expr_extensions.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 15 | ||||
-rw-r--r-- | docs/user/assists.md | 19 |
8 files changed, 174 insertions, 35 deletions
diff --git a/crates/ra_assists/src/assists/apply_demorgan.rs b/crates/ra_assists/src/assists/apply_demorgan.rs index 068da1774..7c57c0560 100644 --- a/crates/ra_assists/src/assists/apply_demorgan.rs +++ b/crates/ra_assists/src/assists/apply_demorgan.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use super::invert_if::invert_boolean_expression; | ||
1 | use hir::db::HirDatabase; | 2 | use hir::db::HirDatabase; |
2 | use ra_syntax::ast::{self, AstNode}; | 3 | use ra_syntax::ast::{self, AstNode}; |
3 | use ra_syntax::SyntaxNode; | ||
4 | 4 | ||
5 | use crate::{Assist, AssistCtx, AssistId}; | 5 | use crate::{Assist, AssistCtx, AssistId}; |
6 | 6 | ||
@@ -32,18 +32,18 @@ pub(crate) fn apply_demorgan(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> | |||
32 | if !cursor_in_range { | 32 | if !cursor_in_range { |
33 | return None; | 33 | return None; |
34 | } | 34 | } |
35 | let lhs = expr.lhs()?.syntax().clone(); | 35 | let lhs = expr.lhs()?; |
36 | let lhs_range = lhs.text_range(); | 36 | let lhs_range = lhs.syntax().text_range(); |
37 | let rhs = expr.rhs()?.syntax().clone(); | 37 | let rhs = expr.rhs()?; |
38 | let rhs_range = rhs.text_range(); | 38 | let rhs_range = rhs.syntax().text_range(); |
39 | let not_lhs = undo_negation(lhs)?; | 39 | let not_lhs = invert_boolean_expression(&lhs)?; |
40 | let not_rhs = undo_negation(rhs)?; | 40 | let not_rhs = invert_boolean_expression(&rhs)?; |
41 | 41 | ||
42 | ctx.add_assist(AssistId("apply_demorgan"), "apply demorgan's law", |edit| { | 42 | ctx.add_assist(AssistId("apply_demorgan"), "apply demorgan's law", |edit| { |
43 | edit.target(op_range); | 43 | edit.target(op_range); |
44 | edit.replace(op_range, opposite_op); | 44 | edit.replace(op_range, opposite_op); |
45 | edit.replace(lhs_range, format!("!({}", not_lhs)); | 45 | edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); |
46 | edit.replace(rhs_range, format!("{})", not_rhs)); | 46 | edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); |
47 | }) | 47 | }) |
48 | } | 48 | } |
49 | 49 | ||
@@ -56,28 +56,6 @@ fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> { | |||
56 | } | 56 | } |
57 | } | 57 | } |
58 | 58 | ||
59 | // This function tries to undo unary negation, or inequality | ||
60 | fn undo_negation(node: SyntaxNode) -> Option<String> { | ||
61 | match ast::Expr::cast(node)? { | ||
62 | ast::Expr::BinExpr(bin) => match bin.op_kind()? { | ||
63 | ast::BinOp::NegatedEqualityTest => { | ||
64 | let lhs = bin.lhs()?.syntax().text(); | ||
65 | let rhs = bin.rhs()?.syntax().text(); | ||
66 | Some(format!("{} == {}", lhs, rhs)) | ||
67 | } | ||
68 | _ => None, | ||
69 | }, | ||
70 | ast::Expr::PrefixExpr(pe) => match pe.op_kind()? { | ||
71 | ast::PrefixOp::Not => { | ||
72 | let child = pe.expr()?.syntax().text(); | ||
73 | Some(String::from(child)) | ||
74 | } | ||
75 | _ => None, | ||
76 | }, | ||
77 | _ => None, | ||
78 | } | ||
79 | } | ||
80 | |||
81 | #[cfg(test)] | 59 | #[cfg(test)] |
82 | mod tests { | 60 | mod tests { |
83 | use super::*; | 61 | use super::*; |
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..bababa3e2 --- /dev/null +++ b/crates/ra_assists/src/assists/invert_if.rs | |||
@@ -0,0 +1,102 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::ast::{self, AstNode}; | ||
3 | use ra_syntax::T; | ||
4 | |||
5 | use crate::{Assist, AssistCtx, AssistId}; | ||
6 | |||
7 | // Assist: invert_if | ||
8 | // | ||
9 | // Apply invert_if | ||
10 | // This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}` | ||
11 | // This also works with `!=`. This assist can only be applied with the cursor | ||
12 | // on `if`. | ||
13 | // | ||
14 | // ``` | ||
15 | // fn main() { | ||
16 | // if<|> !y { A } else { B } | ||
17 | // } | ||
18 | // ``` | ||
19 | // -> | ||
20 | // ``` | ||
21 | // fn main() { | ||
22 | // if y { B } else { A } | ||
23 | // } | ||
24 | // ``` | ||
25 | |||
26 | pub(crate) fn invert_if(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
27 | let if_keyword = ctx.find_token_at_offset(T![if])?; | ||
28 | let expr = ast::IfExpr::cast(if_keyword.parent())?; | ||
29 | let if_range = if_keyword.text_range(); | ||
30 | let cursor_in_range = ctx.frange.range.is_subrange(&if_range); | ||
31 | if !cursor_in_range { | ||
32 | return None; | ||
33 | } | ||
34 | |||
35 | let cond = expr.condition()?.expr()?; | ||
36 | let then_node = expr.then_branch()?.syntax().clone(); | ||
37 | |||
38 | if let ast::ElseBranch::Block(else_block) = expr.else_branch()? { | ||
39 | let flip_cond = invert_boolean_expression(&cond)?; | ||
40 | let cond_range = cond.syntax().text_range(); | ||
41 | let else_node = else_block.syntax(); | ||
42 | let else_range = else_node.text_range(); | ||
43 | let then_range = then_node.text_range(); | ||
44 | return ctx.add_assist(AssistId("invert_if"), "invert if branches", |edit| { | ||
45 | edit.target(if_range); | ||
46 | edit.replace(cond_range, flip_cond.syntax().text()); | ||
47 | edit.replace(else_range, then_node.text()); | ||
48 | edit.replace(then_range, else_node.text()); | ||
49 | }); | ||
50 | } | ||
51 | |||
52 | None | ||
53 | } | ||
54 | |||
55 | pub(crate) fn invert_boolean_expression(expr: &ast::Expr) -> Option<ast::Expr> { | ||
56 | match expr { | ||
57 | ast::Expr::BinExpr(bin) => match bin.op_kind()? { | ||
58 | ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()), | ||
59 | _ => None, | ||
60 | }, | ||
61 | ast::Expr::PrefixExpr(pe) => match pe.op_kind()? { | ||
62 | ast::PrefixOp::Not => pe.expr(), | ||
63 | _ => None, | ||
64 | }, | ||
65 | _ => None, | ||
66 | } | ||
67 | } | ||
68 | |||
69 | #[cfg(test)] | ||
70 | mod tests { | ||
71 | use super::*; | ||
72 | |||
73 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
74 | |||
75 | #[test] | ||
76 | fn invert_if_remove_inequality() { | ||
77 | check_assist( | ||
78 | invert_if, | ||
79 | "fn f() { i<|>f x != 3 { 1 } else { 3 + 2 } }", | ||
80 | "fn f() { i<|>f x == 3 { 3 + 2 } else { 1 } }", | ||
81 | ) | ||
82 | } | ||
83 | |||
84 | #[test] | ||
85 | fn invert_if_remove_not() { | ||
86 | check_assist( | ||
87 | invert_if, | ||
88 | "fn f() { <|>if !cond { 3 * 2 } else { 1 } }", | ||
89 | "fn f() { <|>if cond { 1 } else { 3 * 2 } }", | ||
90 | ) | ||
91 | } | ||
92 | |||
93 | #[test] | ||
94 | fn invert_if_doesnt_apply_with_cursor_not_on_if() { | ||
95 | check_assist_not_applicable(invert_if, "fn f() { if !<|>cond { 3 * 2 } else { 1 } }") | ||
96 | } | ||
97 | |||
98 | #[test] | ||
99 | fn invert_if_doesnt_apply_without_negated() { | ||
100 | check_assist_not_applicable(invert_if, "fn f() { i<|>f cond { 3 * 2 } else { 1 } }") | ||
101 | } | ||
102 | } | ||
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs index 176761efb..3c716c2d1 100644 --- a/crates/ra_assists/src/doc_tests/generated.rs +++ b/crates/ra_assists/src/doc_tests/generated.rs | |||
@@ -342,6 +342,23 @@ fn main() { | |||
342 | } | 342 | } |
343 | 343 | ||
344 | #[test] | 344 | #[test] |
345 | fn doctest_invert_if() { | ||
346 | check( | ||
347 | "invert_if", | ||
348 | r#####" | ||
349 | fn main() { | ||
350 | if<|> !y { A } else { B } | ||
351 | } | ||
352 | "#####, | ||
353 | r#####" | ||
354 | fn main() { | ||
355 | if y { B } else { A } | ||
356 | } | ||
357 | "#####, | ||
358 | ) | ||
359 | } | ||
360 | |||
361 | #[test] | ||
345 | fn doctest_make_raw_string() { | 362 | fn doctest_make_raw_string() { |
346 | check( | 363 | check( |
347 | "make_raw_string", | 364 | "make_raw_string", |
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 { | |||
97 | mod add_impl; | 97 | mod add_impl; |
98 | mod add_new; | 98 | mod add_new; |
99 | mod apply_demorgan; | 99 | mod apply_demorgan; |
100 | mod invert_if; | ||
100 | mod flip_comma; | 101 | mod flip_comma; |
101 | mod flip_binexpr; | 102 | mod flip_binexpr; |
102 | mod flip_trait_bound; | 103 | mod flip_trait_bound; |
@@ -122,6 +123,7 @@ mod assists { | |||
122 | add_impl::add_impl, | 123 | add_impl::add_impl, |
123 | add_new::add_new, | 124 | add_new::add_new, |
124 | apply_demorgan::apply_demorgan, | 125 | apply_demorgan::apply_demorgan, |
126 | invert_if::invert_if, | ||
125 | change_visibility::change_visibility, | 127 | change_visibility::change_visibility, |
126 | fill_match_arms::fill_match_arms, | 128 | fill_match_arms::fill_match_arms, |
127 | merge_match_arms::merge_match_arms, | 129 | merge_match_arms::merge_match_arms, |
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::{ | |||
13 | make::{self, tokens}, | 13 | make::{self, tokens}, |
14 | AstNode, TypeBoundsOwner, | 14 | AstNode, TypeBoundsOwner, |
15 | }, | 15 | }, |
16 | AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, | 16 | AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, SyntaxKind, |
17 | SyntaxKind::{ATTR, COMMENT, WHITESPACE}, | 17 | SyntaxKind::{ATTR, COMMENT, WHITESPACE}, |
18 | SyntaxNode, SyntaxToken, T, | 18 | SyntaxNode, SyntaxToken, T, |
19 | }; | 19 | }; |
20 | 20 | ||
21 | impl ast::BinExpr { | ||
22 | #[must_use] | ||
23 | pub fn replace_op(&self, op: SyntaxKind) -> Option<ast::BinExpr> { | ||
24 | let op_node: SyntaxElement = self.op_details()?.0.into(); | ||
25 | let to_insert: Option<SyntaxElement> = Some(tokens::op(op).into()); | ||
26 | let replace_range = RangeInclusive::new(op_node.clone(), op_node); | ||
27 | Some(replace_children(self, replace_range, to_insert.into_iter())) | ||
28 | } | ||
29 | } | ||
30 | |||
21 | impl ast::FnDef { | 31 | impl ast::FnDef { |
22 | #[must_use] | 32 | #[must_use] |
23 | pub fn with_body(&self, body: ast::Block) -> ast::FnDef { | 33 | 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 { | |||
127 | } | 127 | } |
128 | 128 | ||
129 | impl ast::BinExpr { | 129 | impl ast::BinExpr { |
130 | fn op_details(&self) -> Option<(SyntaxToken, BinOp)> { | 130 | pub fn op_details(&self) -> Option<(SyntaxToken, BinOp)> { |
131 | self.syntax().children_with_tokens().filter_map(|it| it.into_token()).find_map(|c| { | 131 | self.syntax().children_with_tokens().filter_map(|it| it.into_token()).find_map(|c| { |
132 | let bin_op = match c.kind() { | 132 | let bin_op = match c.kind() { |
133 | T![||] => BinOp::BooleanOr, | 133 | 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<N: AstNode>(text: &str) -> N { | |||
173 | } | 173 | } |
174 | 174 | ||
175 | pub mod tokens { | 175 | pub mod tokens { |
176 | use crate::{AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken, T}; | 176 | use crate::{AstNode, Parse, SourceFile, SyntaxKind, SyntaxKind::*, SyntaxToken, T}; |
177 | use once_cell::sync::Lazy; | 177 | use once_cell::sync::Lazy; |
178 | 178 | ||
179 | static SOURCE_FILE: Lazy<Parse<SourceFile>> = Lazy::new(|| SourceFile::parse(",\n; ;")); | 179 | static SOURCE_FILE: Lazy<Parse<SourceFile>> = |
180 | Lazy::new(|| SourceFile::parse("const C: () = (1 != 1, 2 == 2)\n;")); | ||
181 | |||
182 | pub fn op(op: SyntaxKind) -> SyntaxToken { | ||
183 | SOURCE_FILE | ||
184 | .tree() | ||
185 | .syntax() | ||
186 | .descendants_with_tokens() | ||
187 | .filter_map(|it| it.into_token()) | ||
188 | .find(|it| it.kind() == op) | ||
189 | .unwrap() | ||
190 | } | ||
180 | 191 | ||
181 | pub fn comma() -> SyntaxToken { | 192 | pub fn comma() -> SyntaxToken { |
182 | SOURCE_FILE | 193 | SOURCE_FILE |
diff --git a/docs/user/assists.md b/docs/user/assists.md index 8da7578e2..6f4c30bee 100644 --- a/docs/user/assists.md +++ b/docs/user/assists.md | |||
@@ -329,6 +329,25 @@ fn main() { | |||
329 | } | 329 | } |
330 | ``` | 330 | ``` |
331 | 331 | ||
332 | ## `invert_if` | ||
333 | |||
334 | Apply invert_if | ||
335 | This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}` | ||
336 | This also works with `!=`. This assist can only be applied with the cursor | ||
337 | on `if`. | ||
338 | |||
339 | ```rust | ||
340 | // BEFORE | ||
341 | fn main() { | ||
342 | if┃ !y { A } else { B } | ||
343 | } | ||
344 | |||
345 | // AFTER | ||
346 | fn main() { | ||
347 | if y { B } else { A } | ||
348 | } | ||
349 | ``` | ||
350 | |||
332 | ## `make_raw_string` | 351 | ## `make_raw_string` |
333 | 352 | ||
334 | Adds `r#` to a plain string literal. | 353 | Adds `r#` to a plain string literal. |