Skip to content

Commit 0444838

Browse files
FMorschelCommit Queue
authored andcommitted
[DAS] Fixes broken cases for "Convert to boolean expression" fix
Fixes: #60614 Change-Id: Ib083c4150cacc4e0e8cf3a2a37916fd5894d116b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/440203 Reviewed-by: Samuel Rawlins <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]> Commit-Queue: Konstantin Shcheglov <[email protected]> Auto-Submit: Felipe Morschel <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]>
1 parent 5ac8423 commit 0444838

File tree

2 files changed

+127
-66
lines changed

2 files changed

+127
-66
lines changed

pkg/analysis_server/lib/src/services/correction/dart/convert_to_boolean_expression.dart

Lines changed: 75 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,6 @@ import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dar
1212
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
1313
import 'package:analyzer_plugin/utilities/range_factory.dart';
1414

15-
/// This correction producer can act on a variety of code, and various edits
16-
/// might be applied.
17-
typedef _ParametersFixData =
18-
({
19-
SourceRange parensRange,
20-
List<SourceRange> deleteRanges,
21-
bool negated,
22-
bool needsParens,
23-
(SourceRange, String)? replace,
24-
bool bangBeforeParens,
25-
});
26-
2715
class ConvertToBooleanExpression extends ResolvedCorrectionProducer {
2816
ConvertToBooleanExpression({required super.context});
2917

@@ -41,31 +29,31 @@ class ConvertToBooleanExpression extends ResolvedCorrectionProducer {
4129
Future<void> compute(ChangeBuilder builder) async {
4230
AstNode? node = this.node;
4331
if (node is BooleanLiteral) node = node.parent;
44-
_ParametersFixData parameters;
4532

4633
if (node case BinaryExpression(
4734
:var rightOperand,
4835
:var leftOperand,
4936
operator: Token(type: var operator),
5037
)) {
5138
if (rightOperand is BooleanLiteral) {
52-
parameters = _processBinaryExp(
39+
return await _processBinaryExp(
40+
builder,
5341
node,
5442
operator,
5543
rightOperand,
5644
leftOperand,
5745
currentIsLeft: false,
5846
);
59-
} else if (leftOperand is BooleanLiteral) {
60-
parameters = _processBinaryExp(
47+
}
48+
if (leftOperand is BooleanLiteral) {
49+
return await _processBinaryExp(
50+
builder,
6151
node,
6252
operator,
6353
leftOperand,
6454
rightOperand,
6555
currentIsLeft: true,
6656
);
67-
} else {
68-
return;
6957
}
7058
} else if (node
7159
case ConditionalExpression(
@@ -74,8 +62,8 @@ class ConvertToBooleanExpression extends ResolvedCorrectionProducer {
7462
:var elseExpression,
7563
) &&
7664
var conditionalExp) {
77-
_ParametersFixData? result = (switch ((thenExpression, elseExpression)) {
78-
(BooleanLiteral then, BooleanLiteral elseExp) => () {
65+
await (switch ((thenExpression, elseExpression)) {
66+
(BooleanLiteral then, BooleanLiteral elseExp) => () async {
7967
var equalValues = then.value == elseExp.value;
8068
var rangeStart =
8169
equalValues
@@ -85,76 +73,88 @@ class ConvertToBooleanExpression extends ResolvedCorrectionProducer {
8573
: range.endEnd(condition, then);
8674
// remove ` : elseExp`
8775
var rangeEnd = range.endEnd(then, elseExp);
88-
return (
76+
await _addEdit(
77+
builder,
8978
parensRange: range.node(equalValues ? then : condition),
9079
deleteRanges: [rangeStart, rangeEnd],
9180
negated: !then.value && elseExp.value,
92-
replace: null,
93-
needsParens: !equalValues && condition.needsParens,
81+
needsParens: !equalValues && condition.needsParens(),
9482
bangBeforeParens: false,
9583
);
9684
}(),
97-
(BooleanLiteral then, Expression elseExp) => () {
85+
(BooleanLiteral then, Expression elseExp) => () async {
9886
var replaceRange = range.endStart(condition, elseExp);
9987
var operator = then.ifBarElseAmpersand;
100-
return (
101-
parensRange: range.node(conditionalExp),
88+
await _addEdit(
89+
builder,
90+
parensRange: range.node(conditionalExp.condition),
10291
deleteRanges: const <SourceRange>[],
10392
negated: !then.value,
10493
replace: (replaceRange, ' ${operator.lexeme} '),
105-
// conditional expressions always need parens so there will
106-
// be no need to add them
107-
needsParens: false,
108-
bangBeforeParens: false,
94+
needsParens: conditionalExp.condition.needsParens(
95+
then.value ? operator : null,
96+
),
97+
bangBeforeParens: true,
98+
parensRange2:
99+
elseExp.needsParens(operator) ? range.node(elseExp) : null,
109100
);
110101
}(),
111-
(Expression then, BooleanLiteral elseExp) => () {
102+
(Expression then, BooleanLiteral elseExp) => () async {
112103
var rangeStart = range.endStart(condition, then);
113104
var rangeEnd = range.endEnd(then, elseExp);
114105
var operator = elseExp.ifBarElseAmpersand;
115-
return (
116-
parensRange: range.node(conditionalExp),
106+
await _addEdit(
107+
builder,
108+
parensRange: range.node(conditionalExp.condition),
117109
deleteRanges: [rangeEnd],
118110
negated: elseExp.value,
119111
replace: (rangeStart, ' ${operator.lexeme} '),
120-
// conditional expressions always need parens so there will
121-
// be no need to add them
122-
needsParens: false,
123-
bangBeforeParens: false,
112+
needsParens: conditionalExp.condition.needsParens(
113+
elseExp.value ? null : operator,
114+
),
115+
bangBeforeParens: true,
116+
parensRange2: then.needsParens(operator) ? range.node(then) : null,
124117
);
125118
}(),
126119
(_, _) => null,
127120
});
128-
if (result == null) {
129-
return;
130-
}
131-
parameters = result;
132-
} else {
133-
return;
134121
}
135-
await _addEdit(builder, parameters);
136122
}
137123

124+
/// This correction producer can act on a variety of code, and various edits
125+
/// might be applied.
126+
///
127+
/// The [deleteRanges] are ranges that should be deleted from the code.
128+
///
129+
/// The [replace] will be normally used to replace part of the expression
130+
/// with a new operator.
131+
///
132+
/// The [parensRange] is related to [needsParens] and [bangBeforeParens]. The
133+
/// `bang` is added only if [negated] is true. It was meant for adding the
134+
/// parens when the `bang` or the new operator given by [replace] makes the
135+
/// precedence of the expression different than the original one.
136+
///
137+
/// The [parensRange2] is meant to be used when converting conditional
138+
/// expressions and should be non-`null` if the second expression needs
139+
/// parentheses considering the new operator added by the given [replace].
138140
Future<void> _addEdit(
139-
ChangeBuilder builder,
140-
_ParametersFixData parameters,
141-
) async {
142-
var (
143-
:parensRange,
144-
:deleteRanges,
145-
:negated,
146-
:replace,
147-
:needsParens,
148-
:bangBeforeParens,
149-
) = parameters;
141+
ChangeBuilder builder, {
142+
required SourceRange parensRange,
143+
required bool needsParens,
144+
required List<SourceRange> deleteRanges,
145+
required bool negated,
146+
required bool bangBeforeParens,
147+
(SourceRange, String)? replace,
148+
SourceRange? parensRange2,
149+
}) async {
150150
await builder.addDartFileEdit(file, (builder) {
151151
if (bangBeforeParens) {
152152
if (negated) {
153153
builder.addSimpleInsertion(parensRange.offset, TokenType.BANG.lexeme);
154-
if (needsParens) {
155-
builder.addSimpleInsertion(parensRange.offset, '(');
156-
builder.addSimpleInsertion(parensRange.end, ')');
157-
}
154+
}
155+
if (needsParens) {
156+
builder.addSimpleInsertion(parensRange.offset, '(');
157+
builder.addSimpleInsertion(parensRange.end, ')');
158158
}
159159
} else {
160160
if (needsParens) {
@@ -170,19 +170,24 @@ class ConvertToBooleanExpression extends ResolvedCorrectionProducer {
170170
if (replace != null) {
171171
builder.addSimpleReplacement(replace.$1, replace.$2);
172172
}
173+
if (parensRange2 != null) {
174+
builder.addSimpleInsertion(parensRange2.offset, '(');
175+
builder.addSimpleInsertion(parensRange2.end, ')');
176+
}
173177
for (var range in deleteRanges) {
174178
builder.addDeletion(range);
175179
}
176180
});
177181
}
178182

179-
_ParametersFixData _processBinaryExp(
183+
Future<void> _processBinaryExp(
184+
ChangeBuilder builder,
180185
BinaryExpression binaryExp,
181186
TokenType operator,
182187
BooleanLiteral current,
183188
Expression other, {
184189
required bool currentIsLeft,
185-
}) {
190+
}) async {
186191
List<SourceRange> deleteRanges;
187192
SourceRange parensRange;
188193
bool needsParens;
@@ -196,22 +201,22 @@ class ConvertToBooleanExpression extends ResolvedCorrectionProducer {
196201
: range.startStart(other, current),
197202
];
198203
parensRange = range.node(current);
199-
needsParens = current.needsParens;
204+
needsParens = current.needsParens();
200205
default:
201206
deleteRanges = [
202207
currentIsLeft
203208
? range.startStart(current, other)
204209
: range.endEnd(other, current),
205210
];
206211
parensRange = range.node(other);
207-
needsParens = other.needsParens;
212+
needsParens = other.needsParens();
208213
}
209-
return (
214+
await _addEdit(
215+
builder,
210216
negated: !isPositiveCase(binaryExp, current),
211217
parensRange: parensRange,
212218
deleteRanges: deleteRanges,
213219
needsParens: needsParens,
214-
replace: null,
215220
bangBeforeParens: true,
216221
);
217222
}
@@ -238,5 +243,9 @@ extension on BooleanLiteral {
238243
}
239244

240245
extension on Expression {
241-
bool get needsParens => precedence <= Precedence.prefix;
246+
bool needsParens([TokenType? tokenType]) =>
247+
precedence <=
248+
(tokenType != null
249+
? Precedence.forTokenType(tokenType)
250+
: Precedence.prefix);
242251
}

pkg/analysis_server/test/src/services/correction/fix/convert_to_boolean_expression_test.dart

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,58 @@ void f(bool value1, bool value2) {
174174
''');
175175
}
176176

177+
Future<void> test_conditional_expressionCondition_else() async {
178+
await resolveTestCode(r'''
179+
void f(int? value1, bool value2) {
180+
print(value1 == null ? false : value2);
181+
}
182+
''');
183+
await assertHasFix(r'''
184+
void f(int? value1, bool value2) {
185+
print(!(value1 == null) && value2);
186+
}
187+
''');
188+
}
189+
190+
Future<void> test_conditional_expressionCondition_then() async {
191+
await resolveTestCode(r'''
192+
void f(int? value1, bool value2) {
193+
print(value1 == null || value1 == 0 ? value2 : false);
194+
}
195+
''');
196+
await assertHasFix(r'''
197+
void f(int? value1, bool value2) {
198+
print((value1 == null || value1 == 0) && value2);
199+
}
200+
''');
201+
}
202+
203+
Future<void> test_conditional_expressionElse() async {
204+
await resolveTestCode(r'''
205+
void f(bool value1, bool? value2) {
206+
print(value1 ? false : value2 ?? false);
207+
}
208+
''');
209+
await assertHasFix(r'''
210+
void f(bool value1, bool? value2) {
211+
print(!value1 && (value2 ?? false));
212+
}
213+
''');
214+
}
215+
216+
Future<void> test_conditional_expressionThen() async {
217+
await resolveTestCode(r'''
218+
void f(bool value1, bool? value2) {
219+
print(value1 ? value2 ?? false : false);
220+
}
221+
''');
222+
await assertHasFix(r'''
223+
void f(bool value1, bool? value2) {
224+
print(value1 && (value2 ?? false));
225+
}
226+
''');
227+
}
228+
177229
Future<void> test_conditional_thenFalse() async {
178230
await resolveTestCode(r'''
179231
void f(bool value1, bool value2) {

0 commit comments

Comments
 (0)