Add CONVERT_TO_SWITCH_STATEMENT for if-case statement.

Bug: https://github.com/dart-lang/sdk/issues/52068
Change-Id: Ia561a8e711272cfcae690a4de1ea3e39906bef47
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/295724
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/assist_internal.dart b/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
index b73847b..d57f953 100644
--- a/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
@@ -102,6 +102,7 @@
     ConvertConditionalExpressionToIfElement.new,
     ConvertDocumentationIntoBlock.new,
     ConvertDocumentationIntoLine.new,
+    ConvertIfStatementToSwitchStatement.new,
     ConvertIntoAsyncBody.new,
     ConvertIntoBlockBody.new,
     ConvertIntoFinalField.new,
@@ -111,6 +112,7 @@
     ConvertIntoIsNotEmpty.new,
     ConvertMapFromIterableToForLiteral.new,
     ConvertPartOfToUri.new,
+    ConvertSwitchExpressionToSwitchStatement.new,
     ConvertToDoubleQuotes.new,
     ConvertToExpressionFunctionBody.new,
     ConvertToFieldParameter.new,
@@ -127,7 +129,6 @@
     ConvertToSingleQuotes.new,
     ConvertToSuperParameters.new,
     ConvertToSwitchExpression.new,
-    ConvertToSwitchStatement.new,
     DestructureLocalVariableAssignment.new,
     EncapsulateField.new,
     ExchangeOperands.new,
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 d757712..4b47afb 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
@@ -4,6 +4,7 @@
 
 import 'package:analysis_server/src/services/correction/assist.dart';
 import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
+import 'package:analysis_server/src/utilities/extensions/ast.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/src/dart/ast/extensions.dart';
@@ -12,7 +13,138 @@
 import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
 import 'package:analyzer_plugin/utilities/range_factory.dart';
 
-class ConvertToSwitchStatement extends CorrectionProducer {
+class ConvertIfStatementToSwitchStatement extends CorrectionProducer {
+  @override
+  AssistKind get assistKind => DartAssistKind.CONVERT_TO_SWITCH_STATEMENT;
+
+  @override
+  Future<void> compute(ChangeBuilder builder) async {
+    final ifStatement = node;
+    if (ifStatement is! IfStatement) {
+      return;
+    }
+
+    final cases = _buildCases(ifStatement);
+    if (cases == null) {
+      return;
+    }
+
+    final firstThen = cases.firstOrNull;
+    if (firstThen is! _IfCaseThen) {
+      return;
+    }
+
+    final indent = utils.getLinePrefix(ifStatement.offset);
+    final singleIndent = utils.getIndent(1);
+    final caseIndent = '$indent$singleIndent';
+
+    await builder.addDartFileEdit(file, (builder) {
+      builder.addReplacement(range.node(ifStatement), (builder) {
+        final expressionCode = firstThen.identifier;
+        builder.writeln('switch ($expressionCode) {');
+
+        for (final case_ in cases) {
+          switch (case_) {
+            case _IfCaseThen():
+              final patternCode = case_.patternCode;
+              builder.writeln('${caseIndent}case $patternCode:');
+              _writeStatement(
+                builder: builder,
+                statement: case_.statement,
+                ifStatementIndent: indent,
+              );
+            case _IfCaseElse():
+              builder.writeln('${caseIndent}default:');
+              _writeStatement(
+                builder: builder,
+                statement: case_.statement,
+                ifStatementIndent: indent,
+              );
+          }
+        }
+
+        builder.write('$indent}');
+      });
+    });
+  }
+
+  List<_IfCase>? _buildCases(IfStatement ifStatement) {
+    final expression = ifStatement.expression;
+    if (expression is! SimpleIdentifier) {
+      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);
+
+    final elseStatement = ifStatement.elseStatement;
+    if (elseStatement is IfStatement) {
+      final elseCases = _buildCases(elseStatement);
+      if (elseCases == null) {
+        return null;
+      }
+      for (final elseCase in elseCases) {
+        if (elseCase is _IfCaseThen) {
+          if (elseCase.identifier.lexeme != thenCase.identifier.lexeme) {
+            return null;
+          }
+        }
+        cases.add(elseCase);
+      }
+    } else if (elseStatement != null) {
+      cases.add(
+        _IfCaseElse(
+          statement: elseStatement,
+        ),
+      );
+    }
+
+    return cases;
+  }
+
+  /// Writes [statement], if it is a [Block], inlines it.
+  void _writeStatement({
+    required DartEditBuilder builder,
+    required Statement statement,
+    required String ifStatementIndent,
+  }) {
+    final statements = statement.selfOrBlockStatements;
+    final range = utils.getLinesRangeStatements(statements);
+
+    // if
+    //   statement
+    // switch
+    //   case
+    //     statement
+    final singleIndent = utils.getIndent(1);
+    final newIndent = '$ifStatementIndent$singleIndent';
+
+    final code = utils.replaceSourceRangeIndent(
+      range,
+      ifStatementIndent,
+      newIndent,
+    );
+
+    builder.write(code);
+  }
+}
+
+class ConvertSwitchExpressionToSwitchStatement extends CorrectionProducer {
   @override
   AssistKind get assistKind => DartAssistKind.CONVERT_TO_SWITCH_STATEMENT;
 
@@ -209,6 +341,31 @@
   }
 }
 
+sealed class _IfCase {
+  final Statement statement;
+
+  _IfCase({
+    required this.statement,
+  });
+}
+
+class _IfCaseElse extends _IfCase {
+  _IfCaseElse({
+    required super.statement,
+  });
+}
+
+class _IfCaseThen extends _IfCase {
+  final Token identifier;
+  final String patternCode;
+
+  _IfCaseThen({
+    required this.identifier,
+    required this.patternCode,
+    required super.statement,
+  });
+}
+
 extension on GuardedPattern {
   bool get isPureUntypedWildcard {
     if (whenClause == null) {
diff --git a/pkg/analysis_server/lib/src/utilities/extensions/ast.dart b/pkg/analysis_server/lib/src/utilities/extensions/ast.dart
index 92b99c2..31423f7 100644
--- a/pkg/analysis_server/lib/src/utilities/extensions/ast.dart
+++ b/pkg/analysis_server/lib/src/utilities/extensions/ast.dart
@@ -270,6 +270,13 @@
   }
 }
 
+extension StatementExtension on Statement {
+  List<Statement> get selfOrBlockStatements {
+    final self = this;
+    return self is Block ? self.statements : [self];
+  }
+}
+
 extension TokenQuestionExtension on Token? {
   Token? get asFinalKeyword {
     final self = this;
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 880c285..50adac7 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
@@ -10,12 +10,203 @@
 
 void main() {
   defineReflectiveSuite(() {
-    defineReflectiveTests(ConvertToSwitchStatementTest);
+    defineReflectiveTests(ConvertIfStatementToSwitchStatementTest);
+    defineReflectiveTests(ConvertSwitchExpressionToSwitchStatementTest);
   });
 }
 
 @reflectiveTest
-class ConvertToSwitchStatementTest extends AssistProcessorTest {
+class ConvertIfStatementToSwitchStatementTest extends AssistProcessorTest {
+  @override
+  AssistKind get kind => DartAssistKind.CONVERT_TO_SWITCH_STATEMENT;
+
+  Future<void> test_chain2_case_case_differentIdentifier() async {
+    await resolveTestCode('''
+void f(Object? x, Object? y) {
+  if (x case int()) {
+    0;
+  } else if (y case double()) {
+    1;
+  }
+}
+''');
+    await assertNoAssistAt('if');
+  }
+
+  Future<void> test_chain2_case_case_elseBlock() async {
+    await resolveTestCode('''
+void f(Object? x) {
+  if (x case int()) {
+    0;
+  } else if (x case double()) {
+    1;
+  } else {
+    2;
+  }
+}
+''');
+    await assertHasAssistAt('if', '''
+void f(Object? x) {
+  switch (x) {
+    case int():
+      0;
+    case double():
+      1;
+    default:
+      2;
+  }
+}
+''');
+  }
+
+  Future<void> test_chain2_case_case_noElse() async {
+    await resolveTestCode('''
+void f(Object? x) {
+  if (x case int()) {
+    0;
+  } else if (x case double()) {
+    1;
+  }
+}
+''');
+    await assertHasAssistAt('if', '''
+void f(Object? x) {
+  switch (x) {
+    case int():
+      0;
+    case double():
+      1;
+  }
+}
+''');
+  }
+
+  Future<void> test_chain2_case_case_notIdentifier() async {
+    await resolveTestCode('''
+void f(Object? x) {
+  if (x case int()) {
+    0;
+  } else if (x != null case true) {
+    1;
+  }
+}
+''');
+    await assertNoAssistAt('if');
+  }
+
+  Future<void> test_single_case_thenBlock() async {
+    await resolveTestCode('''
+void f(Object? x) {
+  if (x case int()) {
+    0;
+  }
+}
+''');
+    await assertHasAssistAt('if', '''
+void f(Object? x) {
+  switch (x) {
+    case int():
+      0;
+  }
+}
+''');
+  }
+
+  Future<void> test_single_case_thenBlock_elseBlock() async {
+    await resolveTestCode('''
+void f(Object? x) {
+  if (x case int()) {
+    0;
+  } else {
+    1;
+  }
+}
+''');
+    await assertHasAssistAt('if', '''
+void f(Object? x) {
+  switch (x) {
+    case int():
+      0;
+    default:
+      1;
+  }
+}
+''');
+  }
+
+  Future<void> test_single_case_thenBlock_elseBlockEmpty() async {
+    await resolveTestCode('''
+void f(Object? x) {
+  if (x case int()) {
+    0;
+  } else {}
+}
+''');
+    await assertHasAssistAt('if', '''
+void f(Object? x) {
+  switch (x) {
+    case int():
+      0;
+    default:
+  }
+}
+''');
+  }
+
+  Future<void> test_single_case_thenBlock_elseStatement() async {
+    await resolveTestCode('''
+void f(Object? x) {
+  if (x case int()) {
+    0;
+  } else
+    1;
+}
+''');
+    await assertHasAssistAt('if', '''
+void f(Object? x) {
+  switch (x) {
+    case int():
+      0;
+    default:
+      1;
+  }
+}
+''');
+  }
+
+  Future<void> test_single_case_thenStatement() async {
+    await resolveTestCode('''
+void f(Object? x) {
+  if (x case int())
+    0;
+}
+''');
+    await assertHasAssistAt('if', '''
+void f(Object? x) {
+  switch (x) {
+    case int():
+      0;
+  }
+}
+''');
+  }
+
+  Future<void> test_single_expression_notSupported() async {
+    await resolveTestCode('''
+void f(Object? x) {
+  if (validate(x)) {
+    0;
+  }
+}
+
+bool validate(Object? x) => false;
+''');
+    await assertNoAssistAt('if');
+  }
+}
+
+@reflectiveTest
+class ConvertSwitchExpressionToSwitchStatementTest extends AssistProcessorTest {
   @override
   AssistKind get kind => DartAssistKind.CONVERT_TO_SWITCH_STATEMENT;