Skip to content

Commit 0541ad3

Browse files
FMorschelCommit Queue
authored andcommitted
[DAS] Broadens the scope of no_literal_bool_comparisons
Fixes: #60614 Change-Id: I949cc9790468ccd14a1354c92945138310b5a659 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/430901 Auto-Submit: Felipe Morschel <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
1 parent 297cad2 commit 0541ad3

File tree

5 files changed

+666
-54
lines changed

5 files changed

+666
-54
lines changed

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

Lines changed: 192 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,24 @@ import 'package:analysis_server/src/services/correction/fix.dart';
66
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
77
import 'package:analyzer/dart/ast/ast.dart';
88
import 'package:analyzer/dart/ast/precedence.dart';
9+
import 'package:analyzer/dart/ast/token.dart';
10+
import 'package:analyzer/source/source_range.dart';
911
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
1012
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
1113
import 'package:analyzer_plugin/utilities/range_factory.dart';
1214

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+
1327
class ConvertToBooleanExpression extends ResolvedCorrectionProducer {
1428
ConvertToBooleanExpression({required super.context});
1529

@@ -27,49 +41,202 @@ class ConvertToBooleanExpression extends ResolvedCorrectionProducer {
2741
Future<void> compute(ChangeBuilder builder) async {
2842
AstNode? node = this.node;
2943
if (node is BooleanLiteral) node = node.parent;
30-
if (node is! BinaryExpression) return;
31-
32-
var rightOperand = node.rightOperand;
33-
var leftOperand = node.leftOperand;
34-
35-
Expression expression;
36-
BooleanLiteral literal;
37-
38-
var deleteRange = range.endEnd(leftOperand, rightOperand);
44+
_ParametersFixData parameters;
3945

40-
if (rightOperand is BooleanLiteral) {
41-
literal = rightOperand;
42-
expression = node.leftOperand;
43-
} else if (leftOperand is BooleanLiteral) {
44-
literal = leftOperand;
45-
expression = node.rightOperand;
46-
deleteRange = range.startStart(leftOperand, rightOperand);
46+
if (node case BinaryExpression(
47+
:var rightOperand,
48+
:var leftOperand,
49+
operator: Token(type: var operator),
50+
)) {
51+
if (rightOperand is BooleanLiteral) {
52+
parameters = _processBinaryExp(
53+
node,
54+
operator,
55+
rightOperand,
56+
leftOperand,
57+
currentIsLeft: false,
58+
);
59+
} else if (leftOperand is BooleanLiteral) {
60+
parameters = _processBinaryExp(
61+
node,
62+
operator,
63+
leftOperand,
64+
rightOperand,
65+
currentIsLeft: true,
66+
);
67+
} else {
68+
return;
69+
}
70+
} else if (node
71+
case ConditionalExpression(
72+
:var condition,
73+
:var thenExpression,
74+
:var elseExpression,
75+
) &&
76+
var conditionalExp) {
77+
_ParametersFixData? result = (switch ((thenExpression, elseExpression)) {
78+
(BooleanLiteral then, BooleanLiteral elseExp) => () {
79+
var equalValues = then.value == elseExp.value;
80+
var rangeStart =
81+
equalValues
82+
// keep `then`
83+
? range.startStart(condition, then)
84+
// keep `condition`
85+
: range.endEnd(condition, then);
86+
// remove ` : elseExp`
87+
var rangeEnd = range.endEnd(then, elseExp);
88+
return (
89+
parensRange: range.node(equalValues ? then : condition),
90+
deleteRanges: [rangeStart, rangeEnd],
91+
negated: !then.value && elseExp.value,
92+
replace: null,
93+
needsParens: !equalValues && condition.needsParens,
94+
bangBeforeParens: false,
95+
);
96+
}(),
97+
(BooleanLiteral then, Expression elseExp) => () {
98+
var replaceRange = range.endStart(condition, elseExp);
99+
var operator = then.ifBarElseAmpersand;
100+
return (
101+
parensRange: range.node(conditionalExp),
102+
deleteRanges: const <SourceRange>[],
103+
negated: !then.value,
104+
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,
109+
);
110+
}(),
111+
(Expression then, BooleanLiteral elseExp) => () {
112+
var rangeStart = range.endStart(condition, then);
113+
var rangeEnd = range.endEnd(then, elseExp);
114+
var operator = elseExp.ifBarElseAmpersand;
115+
return (
116+
parensRange: range.node(conditionalExp),
117+
deleteRanges: [rangeEnd],
118+
negated: elseExp.value,
119+
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,
124+
);
125+
}(),
126+
(_, _) => null,
127+
});
128+
if (result == null) {
129+
return;
130+
}
131+
parameters = result;
47132
} else {
48133
return;
49134
}
135+
await _addEdit(builder, parameters);
136+
}
50137

51-
var negated = !isPositiveCase(node, literal);
138+
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;
52150
await builder.addDartFileEdit(file, (builder) {
53-
if (negated) {
54-
builder.addSimpleInsertion(expression.offset, '!');
55-
if (expression.needsParens) {
56-
builder.addSimpleInsertion(expression.offset, '(');
57-
builder.addSimpleInsertion(expression.end, ')');
151+
if (bangBeforeParens) {
152+
if (negated) {
153+
builder.addSimpleInsertion(parensRange.offset, TokenType.BANG.lexeme);
154+
if (needsParens) {
155+
builder.addSimpleInsertion(parensRange.offset, '(');
156+
builder.addSimpleInsertion(parensRange.end, ')');
157+
}
158+
}
159+
} else {
160+
if (needsParens) {
161+
builder.addSimpleInsertion(parensRange.offset, '(');
58162
}
163+
if (negated) {
164+
builder.addSimpleInsertion(parensRange.offset, TokenType.BANG.lexeme);
165+
}
166+
if (needsParens) {
167+
builder.addSimpleInsertion(parensRange.end, ')');
168+
}
169+
}
170+
if (replace != null) {
171+
builder.addSimpleReplacement(replace.$1, replace.$2);
172+
}
173+
for (var range in deleteRanges) {
174+
builder.addDeletion(range);
59175
}
60-
builder.addDeletion(deleteRange);
61176
});
62177
}
63178

179+
_ParametersFixData _processBinaryExp(
180+
BinaryExpression binaryExp,
181+
TokenType operator,
182+
BooleanLiteral current,
183+
Expression other, {
184+
required bool currentIsLeft,
185+
}) {
186+
List<SourceRange> deleteRanges;
187+
SourceRange parensRange;
188+
bool needsParens;
189+
switch (operator) {
190+
case TokenType.BAR || TokenType.BAR_BAR when current.value:
191+
case TokenType.AMPERSAND || TokenType.AMPERSAND_AMPERSAND
192+
when !current.value:
193+
deleteRanges = [
194+
currentIsLeft
195+
? range.endEnd(current, other)
196+
: range.startStart(other, current),
197+
];
198+
parensRange = range.node(current);
199+
needsParens = current.needsParens;
200+
default:
201+
deleteRanges = [
202+
currentIsLeft
203+
? range.startStart(current, other)
204+
: range.endEnd(other, current),
205+
];
206+
parensRange = range.node(other);
207+
needsParens = other.needsParens;
208+
}
209+
return (
210+
negated: !isPositiveCase(binaryExp, current),
211+
parensRange: parensRange,
212+
deleteRanges: deleteRanges,
213+
needsParens: needsParens,
214+
replace: null,
215+
bangBeforeParens: true,
216+
);
217+
}
218+
64219
static bool isPositiveCase(
65220
BinaryExpression expression,
66221
BooleanLiteral literal,
67222
) {
68-
if (expression.operator.lexeme == '==') return literal.value;
69-
return !literal.value;
223+
return switch (expression.operator.type) {
224+
TokenType.BAR ||
225+
TokenType.BAR_BAR ||
226+
TokenType.AMPERSAND ||
227+
TokenType.AMPERSAND_AMPERSAND => true,
228+
TokenType.EQ_EQ => literal.value,
229+
TokenType.BANG_EQ || TokenType.CARET => !literal.value,
230+
_ => throw StateError('Unexpected operator ${expression.operator.type}'),
231+
};
70232
}
71233
}
72234

235+
extension on BooleanLiteral {
236+
TokenType get ifBarElseAmpersand =>
237+
value ? TokenType.BAR_BAR : TokenType.AMPERSAND_AMPERSAND;
238+
}
239+
73240
extension on Expression {
74241
bool get needsParens => precedence <= Precedence.prefix;
75242
}

pkg/analysis_server/test/abstract_single_unit.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ import 'abstract_context.dart';
1818
class AbstractSingleUnitTest extends AbstractContextTest {
1919
bool verifyNoTestUnitErrors = true;
2020

21+
/// Whether the test code should parse carrets as position shorthands.
22+
///
23+
/// Set this to `true` when the test code is using the carret operator.
24+
bool keepCaret = false;
25+
2126
TestCode? _parsedTestCode;
2227
late ParsedUnitResult testParsedResult;
2328
late ResolvedLibraryResult? testLibraryResult;
@@ -38,7 +43,10 @@ class AbstractSingleUnitTest extends AbstractContextTest {
3843

3944
String get testCode => parsedTestCode.code;
4045
set testCode(String value) {
41-
parsedTestCode = TestCode.parse(normalizeSource(value));
46+
parsedTestCode = TestCode.parse(
47+
normalizeSource(value),
48+
positionShorthand: !keepCaret,
49+
);
4250
}
4351

4452
void addTestSource(String code) {

0 commit comments

Comments
 (0)