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) {