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;