Support for converting 'if' statements with bool conditions into patterns.

Bug: https://github.com/dart-lang/sdk/issues/52068
Change-Id: I187b3e3cad66eff7a266478df0c09ffb3d642e34
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/296067
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/convert_to_switch_statement.dart b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_switch_statement.dart
index 4b47afb..f3045cf 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/convert_to_switch_statement.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_switch_statement.dart
@@ -40,7 +40,7 @@
 
     await builder.addDartFileEdit(file, (builder) {
       builder.addReplacement(range.node(ifStatement), (builder) {
-        final expressionCode = firstThen.identifier;
+        final expressionCode = firstThen.expressionCode;
         builder.writeln('switch ($expressionCode) {');
 
         for (final case_ in cases) {
@@ -69,26 +69,11 @@
   }
 
   List<_IfCase>? _buildCases(IfStatement ifStatement) {
-    final expression = ifStatement.expression;
-    if (expression is! SimpleIdentifier) {
+    final thenCase = _buildThenCase(ifStatement);
+    if (thenCase == null) {
       return null;
     }
 
-    final String patternCode;
-    final caseClause = ifStatement.caseClause;
-    if (caseClause != null) {
-      patternCode = utils.getNodeText(caseClause.guardedPattern);
-    } else {
-      // TODO(scheglov) support converting conditions to patterns
-      return null;
-    }
-
-    final thenCase = _IfCaseThen(
-      identifier: expression.token,
-      patternCode: patternCode,
-      statement: ifStatement.thenStatement,
-    );
-
     final cases = <_IfCase>[];
     cases.add(thenCase);
 
@@ -100,7 +85,7 @@
       }
       for (final elseCase in elseCases) {
         if (elseCase is _IfCaseThen) {
-          if (elseCase.identifier.lexeme != thenCase.identifier.lexeme) {
+          if (elseCase.expressionCode != thenCase.expressionCode) {
             return null;
           }
         }
@@ -117,6 +102,36 @@
     return cases;
   }
 
+  _IfCaseThen? _buildThenCase(IfStatement ifStatement) {
+    final expression = ifStatement.expression;
+    final caseClause = ifStatement.caseClause;
+
+    if (caseClause != null) {
+      if (expression is! SimpleIdentifier) {
+        return null;
+      }
+      final guardedPattern = caseClause.guardedPattern;
+      final patternCode = utils.getNodeText(guardedPattern);
+      return _IfCaseThen(
+        expressionCode: expression.token.lexeme,
+        patternCode: patternCode,
+        statement: ifStatement.thenStatement,
+      );
+    }
+
+    // The expression is the bool condition.
+    final result = utils.patternOfBoolCondition(expression);
+    if (result == null) {
+      return null;
+    }
+
+    return _IfCaseThen(
+      expressionCode: result.expressionCode,
+      patternCode: result.patternCode,
+      statement: ifStatement.thenStatement,
+    );
+  }
+
   /// Writes [statement], if it is a [Block], inlines it.
   void _writeStatement({
     required DartEditBuilder builder,
@@ -356,11 +371,11 @@
 }
 
 class _IfCaseThen extends _IfCase {
-  final Token identifier;
+  final String expressionCode;
   final String patternCode;
 
   _IfCaseThen({
-    required this.identifier,
+    required this.expressionCode,
     required this.patternCode,
     required super.statement,
   });
diff --git a/pkg/analysis_server/lib/src/services/correction/util.dart b/pkg/analysis_server/lib/src/services/correction/util.dart
index 1ade87e..893ca72 100644
--- a/pkg/analysis_server/lib/src/services/correction/util.dart
+++ b/pkg/analysis_server/lib/src/services/correction/util.dart
@@ -1038,6 +1038,26 @@
     return InsertionLocation(prefix, offset, suffix);
   }
 
+  ExpressionCasePattern? patternOfBoolCondition(Expression node) {
+    if (node is BinaryExpression) {
+      if (node.isNotEqNull) {
+        final expressionCode = getNodeText(node.leftOperand);
+        return ExpressionCasePattern(
+          expressionCode: expressionCode,
+          patternCode: '_?',
+        );
+      }
+    } else if (node is IsExpression) {
+      final expressionCode = getNodeText(node.expression);
+      final typeCode = getNodeText(node.type);
+      return ExpressionCasePattern(
+        expressionCode: expressionCode,
+        patternCode: '$typeCode()',
+      );
+    }
+    return null;
+  }
+
   InsertionLocation? prepareEnumNewConstructorLocation(
     EnumDeclaration enumDeclaration,
   ) {
@@ -1502,6 +1522,16 @@
   String suffix = '';
 }
 
+class ExpressionCasePattern {
+  final String expressionCode;
+  final String patternCode;
+
+  ExpressionCasePattern({
+    required this.expressionCode,
+    required this.patternCode,
+  });
+}
+
 class InsertionLocation {
   final String prefix;
   final int offset;
diff --git a/pkg/analysis_server/lib/src/utilities/extensions/ast.dart b/pkg/analysis_server/lib/src/utilities/extensions/ast.dart
index 31423f7..228b7b5 100644
--- a/pkg/analysis_server/lib/src/utilities/extensions/ast.dart
+++ b/pkg/analysis_server/lib/src/utilities/extensions/ast.dart
@@ -122,6 +122,12 @@
   }
 }
 
+extension BinaryExpressionExtensions on BinaryExpression {
+  bool get isNotEqNull {
+    return operator.type == TokenType.BANG_EQ && rightOperand is NullLiteral;
+  }
+}
+
 extension CompilationUnitExtension on CompilationUnit {
   /// Return the list of tokens that comprise the file header comment for this
   /// compilation unit.
diff --git a/pkg/analysis_server/test/src/services/correction/assist/convert_to_switch_statement_test.dart b/pkg/analysis_server/test/src/services/correction/assist/convert_to_switch_statement_test.dart
index 50adac7..8615a01 100644
--- a/pkg/analysis_server/test/src/services/correction/assist/convert_to_switch_statement_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/assist/convert_to_switch_statement_test.dart
@@ -20,7 +20,7 @@
   @override
   AssistKind get kind => DartAssistKind.CONVERT_TO_SWITCH_STATEMENT;
 
-  Future<void> test_chain2_case_case_differentIdentifier() async {
+  Future<void> test_chain_case2_differentIdentifier() async {
     await resolveTestCode('''
 void f(Object? x, Object? y) {
   if (x case int()) {
@@ -33,7 +33,7 @@
     await assertNoAssistAt('if');
   }
 
-  Future<void> test_chain2_case_case_elseBlock() async {
+  Future<void> test_chain_case2_elseBlock() async {
     await resolveTestCode('''
 void f(Object? x) {
   if (x case int()) {
@@ -59,7 +59,7 @@
 ''');
   }
 
-  Future<void> test_chain2_case_case_noElse() async {
+  Future<void> test_chain_case2_noElse() async {
     await resolveTestCode('''
 void f(Object? x) {
   if (x case int()) {
@@ -81,7 +81,7 @@
 ''');
   }
 
-  Future<void> test_chain2_case_case_notIdentifier() async {
+  Future<void> test_chain_case2_notIdentifier() async {
     await resolveTestCode('''
 void f(Object? x) {
   if (x case int()) {
@@ -94,6 +94,50 @@
     await assertNoAssistAt('if');
   }
 
+  Future<void> test_chain_case_expression() async {
+    await resolveTestCode('''
+void f(Object? x) {
+  if (x case int()) {
+    0;
+  } else if (x is double) {
+    1;
+  }
+}
+''');
+    await assertHasAssistAt('if', '''
+void f(Object? x) {
+  switch (x) {
+    case int():
+      0;
+    case double():
+      1;
+  }
+}
+''');
+  }
+
+  Future<void> test_chain_expression2() async {
+    await resolveTestCode('''
+void f(Object? x) {
+  if (x is int) {
+    0;
+  } else if (x is double) {
+    1;
+  }
+}
+''');
+    await assertHasAssistAt('if', '''
+void f(Object? x) {
+  switch (x) {
+    case int():
+      0;
+    case double():
+      1;
+  }
+}
+''');
+  }
+
   Future<void> test_single_case_thenBlock() async {
     await resolveTestCode('''
 void f(Object? x) {
@@ -191,6 +235,42 @@
 ''');
   }
 
+  Future<void> test_single_expression_isType() async {
+    await resolveTestCode('''
+void f(Object? x) {
+  if (x is List<int>) {
+    0;
+  }
+}
+''');
+    await assertHasAssistAt('if', '''
+void f(Object? x) {
+  switch (x) {
+    case List<int>():
+      0;
+  }
+}
+''');
+  }
+
+  Future<void> test_single_expression_notEqNull() async {
+    await resolveTestCode('''
+void f(Object? x) {
+  if (x != null) {
+    0;
+  }
+}
+''');
+    await assertHasAssistAt('if', '''
+void f(Object? x) {
+  switch (x) {
+    case _?:
+      0;
+  }
+}
+''');
+  }
+
   Future<void> test_single_expression_notSupported() async {
     await resolveTestCode('''
 void f(Object? x) {