1
+ use std:: collections:: VecDeque ;
2
+
1
3
use syntax:: ast:: { self , AstNode } ;
2
4
3
5
use crate :: { utils:: invert_boolean_expression, AssistContext , AssistId , AssistKind , Assists } ;
@@ -30,19 +32,52 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
30
32
return None ;
31
33
}
32
34
33
- let lhs = expr. lhs ( ) ?;
34
- let lhs_range = lhs. syntax ( ) . text_range ( ) ;
35
- let not_lhs = invert_boolean_expression ( & ctx. sema , lhs) ;
35
+ let mut expr = expr;
36
+
37
+ // Walk up the tree while we have the same binary operator
38
+ while let Some ( parent_expr) = expr. syntax ( ) . parent ( ) . and_then ( ast:: BinExpr :: cast) {
39
+ if let Some ( parent_op) = expr. op_kind ( ) {
40
+ if parent_op == op {
41
+ expr = parent_expr
42
+ }
43
+ }
44
+ }
45
+
46
+ let mut expr_stack = vec ! [ expr. clone( ) ] ;
47
+ let mut terms = Vec :: new ( ) ;
48
+ let mut op_ranges = Vec :: new ( ) ;
49
+
50
+ // Find all the children with the same binary operator
51
+ while let Some ( expr) = expr_stack. pop ( ) {
52
+ let mut traverse_bin_expr_arm = |expr| {
53
+ if let ast:: Expr :: BinExpr ( bin_expr) = expr {
54
+ if let Some ( expr_op) = bin_expr. op_kind ( ) {
55
+ if expr_op == op {
56
+ expr_stack. push ( bin_expr) ;
57
+ } else {
58
+ terms. push ( ast:: Expr :: BinExpr ( bin_expr) ) ;
59
+ }
60
+ } else {
61
+ terms. push ( ast:: Expr :: BinExpr ( bin_expr) ) ;
62
+ }
63
+ } else {
64
+ terms. push ( expr) ;
65
+ }
66
+ } ;
36
67
37
- let rhs = expr. rhs ( ) ?;
38
- let rhs_range = rhs. syntax ( ) . text_range ( ) ;
39
- let not_rhs = invert_boolean_expression ( & ctx. sema , rhs) ;
68
+ op_ranges. extend ( expr. op_token ( ) . map ( |t| t. text_range ( ) ) ) ;
69
+ traverse_bin_expr_arm ( expr. lhs ( ) ?) ;
70
+ traverse_bin_expr_arm ( expr. rhs ( ) ?) ;
71
+ }
40
72
41
73
acc. add (
42
74
AssistId ( "apply_demorgan" , AssistKind :: RefactorRewrite ) ,
43
75
"Apply De Morgan's law" ,
44
76
op_range,
45
77
|edit| {
78
+ terms. sort_by_key ( |t| t. syntax ( ) . text_range ( ) . start ( ) ) ;
79
+ let mut terms = VecDeque :: from ( terms) ;
80
+
46
81
let paren_expr = expr. syntax ( ) . parent ( ) . and_then ( |parent| ast:: ParenExpr :: cast ( parent) ) ;
47
82
48
83
let neg_expr = paren_expr
@@ -57,11 +92,18 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
57
92
}
58
93
} ) ;
59
94
60
- edit. replace ( op_range, opposite_op) ;
95
+ for op_range in op_ranges {
96
+ edit. replace ( op_range, opposite_op) ;
97
+ }
61
98
62
99
if let Some ( paren_expr) = paren_expr {
63
- edit. replace ( lhs_range, not_lhs. syntax ( ) . text ( ) ) ;
64
- edit. replace ( rhs_range, not_rhs. syntax ( ) . text ( ) ) ;
100
+ for term in terms {
101
+ let range = term. syntax ( ) . text_range ( ) ;
102
+ let not_term = invert_boolean_expression ( & ctx. sema , term) ;
103
+
104
+ edit. replace ( range, not_term. syntax ( ) . text ( ) ) ;
105
+ }
106
+
65
107
if let Some ( neg_expr) = neg_expr {
66
108
cov_mark:: hit!( demorgan_double_negation) ;
67
109
edit. replace ( neg_expr. op_token ( ) . unwrap ( ) . text_range ( ) , "" ) ;
@@ -70,8 +112,25 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
70
112
edit. replace ( paren_expr. l_paren_token ( ) . unwrap ( ) . text_range ( ) , "!(" ) ;
71
113
}
72
114
} else {
73
- edit. replace ( lhs_range, format ! ( "!({}" , not_lhs. syntax( ) . text( ) ) ) ;
74
- edit. replace ( rhs_range, format ! ( "{})" , not_rhs. syntax( ) . text( ) ) ) ;
115
+ if let Some ( lhs) = terms. pop_front ( ) {
116
+ let lhs_range = lhs. syntax ( ) . text_range ( ) ;
117
+ let not_lhs = invert_boolean_expression ( & ctx. sema , lhs) ;
118
+
119
+ edit. replace ( lhs_range, format ! ( "!({}" , not_lhs. syntax( ) . text( ) ) ) ;
120
+ }
121
+
122
+ if let Some ( rhs) = terms. pop_back ( ) {
123
+ let rhs_range = rhs. syntax ( ) . text_range ( ) ;
124
+ let not_rhs = invert_boolean_expression ( & ctx. sema , rhs) ;
125
+
126
+ edit. replace ( rhs_range, format ! ( "{})" , not_rhs. syntax( ) . text( ) ) ) ;
127
+ }
128
+
129
+ for term in terms {
130
+ let term_range = term. syntax ( ) . text_range ( ) ;
131
+ let not_term = invert_boolean_expression ( & ctx. sema , term) ;
132
+ edit. replace ( term_range, not_term. syntax ( ) . text ( ) ) ;
133
+ }
75
134
}
76
135
} ,
77
136
)
@@ -179,6 +238,12 @@ fn f() {
179
238
check_assist ( apply_demorgan, "fn f() { x ||$0 x }" , "fn f() { !(!x && !x) }" )
180
239
}
181
240
241
+ #[ test]
242
+ fn demorgan_multiple_terms ( ) {
243
+ check_assist ( apply_demorgan, "fn f() { x ||$0 y || z }" , "fn f() { !(!x && !y && !z) }" ) ;
244
+ check_assist ( apply_demorgan, "fn f() { x || y ||$0 z }" , "fn f() { !(!x && !y && !z) }" ) ;
245
+ }
246
+
182
247
#[ test]
183
248
fn demorgan_doesnt_apply_with_cursor_not_on_op ( ) {
184
249
check_assist_not_applicable ( apply_demorgan, "fn f() { $0 !x || !x }" )
0 commit comments