+ convert_to_boolean_expression correction

Fixes: https://github.com/dart-lang/sdk/issues/52304

Change-Id: Ic080188be5abc70fe1f0da0a7117f31d64a16aa2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/302121
Commit-Queue: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/convert_to_boolean_expression.dart b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_boolean_expression.dart
new file mode 100644
index 0000000..5e736d7
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_boolean_expression.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
+import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:analyzer_plugin/utilities/range_factory.dart';
+
+class ConvertToBooleanExpression extends CorrectionProducer {
+  @override
+  bool get canBeAppliedInBulk => true;
+  @override
+  bool get canBeAppliedToFile => true;
+
+  @override
+  FixKind get fixKind => DartFixKind.CONVERT_TO_BOOL_EXPRESSION;
+
+  @override
+  FixKind get multiFixKind => DartFixKind.CONVERT_TO_BOOL_EXPRESSION_MULTI;
+
+  @override
+  Future<void> compute(ChangeBuilder builder) async {
+    AstNode? node = this.node;
+    if (node is BooleanLiteral) node = node.parent;
+    if (node is! BinaryExpression) return;
+
+    var rightOperand = node.rightOperand;
+    var leftOperand = node.leftOperand;
+
+    Expression expression;
+    BooleanLiteral literal;
+
+    var deleteRange = range.endEnd(leftOperand, rightOperand);
+
+    if (rightOperand is BooleanLiteral) {
+      literal = rightOperand;
+      expression = node.leftOperand;
+    } else if (leftOperand is BooleanLiteral) {
+      literal = leftOperand;
+      expression = node.rightOperand;
+      deleteRange = range.startStart(leftOperand, rightOperand);
+    } else {
+      return;
+    }
+
+    var negated = !isPositiveCase(node, literal);
+    await builder.addDartFileEdit(file, (builder) {
+      if (negated) {
+        builder.addSimpleInsertion(expression.offset, '!');
+      }
+      builder.addDeletion(deleteRange);
+    });
+  }
+
+  static bool isPositiveCase(
+      BinaryExpression expression, BooleanLiteral literal) {
+    if (expression.operator.lexeme == '==') return literal.value;
+    return !literal.value;
+  }
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index b99b44f..e50cbef 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -2140,8 +2140,7 @@
 LintCode.no_leading_underscores_for_local_identifiers:
   status: hasFix
 LintCode.no_literal_bool_comparisons:
-  status: needsFix
-  notes: The fix is to remove `== true` etc.
+  status: hasFix
 LintCode.no_logic_in_create_state:
   status: noFix
 LintCode.no_runtimeType_toString:
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index 3005539..fa6e57d 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -418,6 +418,16 @@
     DartFixKindPriority.IN_FILE,
     'Convert the quotes and remove escapes everywhere in file',
   );
+  static const CONVERT_TO_BOOL_EXPRESSION = FixKind(
+    'dart.fix.convert.toBoolExpression',
+    DartFixKindPriority.DEFAULT,
+    'Convert to boolean expression',
+  );
+  static const CONVERT_TO_BOOL_EXPRESSION_MULTI = FixKind(
+    'dart.fix.convert.toBoolExpression.multi',
+    DartFixKindPriority.DEFAULT,
+    'Convert to boolean expressions everywhere in file',
+  );
   static const CONVERT_TO_CASCADE = FixKind(
     'dart.fix.convert.toCascade',
     DartFixKindPriority.DEFAULT,
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index dce3afc..d431b34 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -55,6 +55,7 @@
 import 'package:analysis_server/src/services/correction/dart/convert_into_is_not.dart';
 import 'package:analysis_server/src/services/correction/dart/convert_map_from_iterable_to_for_literal.dart';
 import 'package:analysis_server/src/services/correction/dart/convert_quotes.dart';
+import 'package:analysis_server/src/services/correction/dart/convert_to_boolean_expression.dart';
 import 'package:analysis_server/src/services/correction/dart/convert_to_cascade.dart';
 import 'package:analysis_server/src/services/correction/dart/convert_to_contains.dart';
 import 'package:analysis_server/src/services/correction/dart/convert_to_expression_function_body.dart';
@@ -539,6 +540,9 @@
     LintNames.no_leading_underscores_for_library_prefixes: [
       RemoveLeadingUnderscore.new,
     ],
+    LintNames.no_literal_bool_comparisons: [
+      ConvertToBooleanExpression.new,
+    ],
     LintNames.no_leading_underscores_for_local_identifiers: [
       RemoveLeadingUnderscore.new,
     ],
diff --git a/pkg/analysis_server/lib/src/services/linter/lint_names.dart b/pkg/analysis_server/lib/src/services/linter/lint_names.dart
index c16ebb7..b04a5d9 100644
--- a/pkg/analysis_server/lib/src/services/linter/lint_names.dart
+++ b/pkg/analysis_server/lib/src/services/linter/lint_names.dart
@@ -76,6 +76,8 @@
   static const String leading_newlines_in_multiline_strings =
       'leading_newlines_in_multiline_strings';
   static const String library_annotations = 'library_annotations';
+  static const String no_literal_bool_comparisons =
+      'no_literal_bool_comparisons';
   static const String no_duplicate_case_values = 'no_duplicate_case_values';
   static const String no_leading_underscores_for_library_prefixes =
       'no_leading_underscores_for_library_prefixes';
diff --git a/pkg/analysis_server/test/src/services/correction/fix/convert_to_boolean_expression_test.dart b/pkg/analysis_server/test/src/services/correction/fix/convert_to_boolean_expression_test.dart
new file mode 100644
index 0000000..dbfdf8b
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/convert_to_boolean_expression_test.dart
@@ -0,0 +1,149 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analysis_server/src/services/linter/lint_names.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'fix_processor.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(ConvertToBoolExpressionBulkTest);
+    defineReflectiveTests(ConvertToBoolExpressionTest);
+  });
+}
+
+@reflectiveTest
+class ConvertToBoolExpressionBulkTest extends BulkFixProcessorTest {
+  @override
+  String get lintCode => LintNames.no_literal_bool_comparisons;
+
+  Future<void> test_singleFile() async {
+    await resolveTestCode('''
+void f(bool value) {
+  if (value != false || value == false) print(value);
+}
+''');
+    await assertHasFix('''
+void f(bool value) {
+  if (value || !value) print(value);
+}
+''');
+  }
+}
+
+@reflectiveTest
+class ConvertToBoolExpressionTest extends FixProcessorLintTest {
+  @override
+  FixKind get kind => DartFixKind.CONVERT_TO_BOOL_EXPRESSION;
+
+  @override
+  String get lintCode => LintNames.no_literal_bool_comparisons;
+
+  Future<void> test_ifFalse() async {
+    await resolveTestCode(r'''
+void f(bool value) {
+ if (value == false) print(value);
+}
+''');
+    await assertHasFix(r'''
+void f(bool value) {
+ if (!value) print(value);
+}
+''');
+  }
+
+  Future<void> test_ifFalse_reversed() async {
+    await resolveTestCode(r'''
+void f(bool value) {
+ if (false == value) print(value);
+}
+''');
+    await assertHasFix(r'''
+void f(bool value) {
+ if (!value) print(value);
+}
+''');
+  }
+
+  Future<void> test_ifNotFalse() async {
+    await resolveTestCode(r'''
+void f(bool value) {
+ if (value != false) print(value);
+}
+''');
+    await assertHasFix(r'''
+void f(bool value) {
+ if (value) print(value);
+}
+''');
+  }
+
+  Future<void> test_ifNotFalse_reversed() async {
+    await resolveTestCode(r'''
+void f(bool value) {
+ if (false != value) print(value);
+}
+''');
+    await assertHasFix(r'''
+void f(bool value) {
+ if (value) print(value);
+}
+''');
+  }
+
+  Future<void> test_ifNotTrue() async {
+    await resolveTestCode(r'''
+void f(bool value) {
+ if (value != true) print(value);
+}
+''');
+    await assertHasFix(r'''
+void f(bool value) {
+ if (!value) print(value);
+}
+''');
+  }
+
+  Future<void> test_ifNotTrue_reversed() async {
+    await resolveTestCode(r'''
+void f(bool value) {
+ if (true != value) print(value);
+}
+''');
+    await assertHasFix(r'''
+void f(bool value) {
+ if (!value) print(value);
+}
+''');
+  }
+
+  Future<void> test_ifTrue() async {
+    await resolveTestCode(r'''
+void f(bool value) {
+ if (value == true) print(value);
+}
+''');
+    await assertHasFix(r'''
+void f(bool value) {
+ if (value) print(value);
+}
+''');
+  }
+
+  Future<void> test_ifTrue_reversed() async {
+    await resolveTestCode(r'''
+void f(bool value) {
+ if (true == value) print(value);
+}
+''');
+    await assertHasFix(r'''
+void f(bool value) {
+ if (value) print(value);
+}
+''');
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
index d212aca..8ae29ae 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
@@ -64,6 +64,8 @@
 import 'convert_into_expression_body_test.dart' as convert_into_expression_body;
 import 'convert_into_is_not_test.dart' as convert_into_is_not;
 import 'convert_quotes_test.dart' as convert_quotes;
+import 'convert_to_boolean_expression_test.dart'
+    as convert_to_boolean_expression;
 import 'convert_to_cascade_test.dart' as convert_to_cascade;
 import 'convert_to_contains_test.dart' as convert_to_contains;
 import 'convert_to_double_quoted_string_test.dart'
@@ -322,6 +324,7 @@
     convert_into_expression_body.main();
     convert_into_is_not.main();
     convert_quotes.main();
+    convert_to_boolean_expression.main();
     convert_to_cascade.main();
     convert_to_contains.main();
     convert_to_double_quoted_string.main();