Issue 52146. Fix ConvertToIfCaseStatementChain for a case without statements.

Bug: https://github.com/dart-lang/sdk/issues/52146
Change-Id: I2947111a0e1a4c6e739bda851f1f91c9637a745a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/297420
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
This commit is contained in:
Konstantin Shcheglov 2023-04-24 19:28:56 +00:00 committed by Commit Queue
parent d3460e0855
commit 2cdfc05568
2 changed files with 208 additions and 29 deletions

View file

@ -5,6 +5,7 @@
import 'package:analysis_server/src/services/correction/assist.dart';
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer_plugin/utilities/assist/assist.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
@ -18,50 +19,43 @@ class ConvertToIfCaseStatementChain extends CorrectionProducer {
@override
Future<void> compute(ChangeBuilder builder) async {
final switchStatement = node;
if (switchStatement is! SwitchStatement) {
if (switchStatement is! SwitchStatementImpl) {
return;
}
final groups = _groups(switchStatement);
if (groups == null) {
return;
}
final ifIndent = utils.getLinePrefix(switchStatement.offset);
final expressionCode = utils.getNodeText(switchStatement.expression);
final switchPatternCases = <SwitchPatternCase>[];
SwitchDefault? defaultCase;
for (final member in switchStatement.members) {
switch (member) {
case SwitchPatternCase():
switchPatternCases.add(member);
case SwitchDefault():
defaultCase = member;
default:
return;
}
}
await builder.addDartFileEdit(file, (builder) {
builder.addReplacement(range.node(switchStatement), (builder) {
var isFirst = true;
for (final case_ in switchPatternCases) {
for (final group in groups) {
if (isFirst) {
isFirst = false;
} else {
builder.write(' else ');
}
final patternCode = utils.getNodeText(case_.guardedPattern);
builder.writeln('if ($expressionCode case $patternCode) {');
switch (group) {
case _SingleCaseGroup():
final patternCode = utils.getNodeText(group.guardedPattern);
builder.writeln('if ($expressionCode case $patternCode) {');
case _JoinedCaseGroup():
final patternCode = group.patterns
.map((pattern) => utils.getNodeText(pattern))
.join(' || ');
builder.writeln('if ($expressionCode case $patternCode) {');
case _DefaultGroup():
builder.writeln('{');
}
_writeStatements(
builder: builder,
blockIndent: ifIndent,
statements: case_.statements,
);
builder.write('$ifIndent}');
}
if (defaultCase case final defaultCase?) {
builder.writeln(' else {');
_writeStatements(
builder: builder,
blockIndent: ifIndent,
statements: defaultCase.statements,
statements: group.statements,
);
builder.write('$ifIndent}');
}
@ -69,14 +63,72 @@ class ConvertToIfCaseStatementChain extends CorrectionProducer {
});
}
List<_Group>? _groups(SwitchStatementImpl switchStatement) {
final result = <_Group>[];
for (final group in switchStatement.memberGroups) {
final members = group.members;
// Support `default`, if alone.
if (members.any((e) => e is SwitchDefault)) {
if (members.length != 1) {
return null;
}
result.add(
_DefaultGroup(
statements: group.statements,
),
);
continue;
}
// We expect only `SwitchPatternCase`s.
final guardedPatterns = members
.whereType<SwitchPatternCase>()
.map((e) => e.guardedPattern)
.toList();
if (guardedPatterns.length != members.length) {
return null;
}
// For single `GuardedPattern` we allow `when`.
final singleGuardedPattern = guardedPatterns.singleOrNull;
if (singleGuardedPattern != null) {
result.add(
_SingleCaseGroup(
guardedPattern: singleGuardedPattern,
statements: group.statements,
),
);
continue;
}
// For joined `GuardedPattern`s, we cannot support any `when`.
if (guardedPatterns.hasWhen) {
return null;
}
result.add(
_JoinedCaseGroup(
patterns: guardedPatterns.map((e) => e.pattern).toList(),
statements: group.statements,
),
);
}
return result;
}
void _writeStatements({
required DartEditBuilder builder,
required List<Statement> statements,
required String blockIndent,
}) {
final range = utils.getLinesRangeStatements(statements);
final first = statements.firstOrNull;
if (first == null) {
return;
}
final firstIndent = utils.getLinePrefix(statements.first.offset);
final range = utils.getLinesRangeStatements(statements);
final firstIndent = utils.getLinePrefix(first.offset);
final singleIndent = utils.getIndent(1);
final code = utils.replaceSourceRangeIndent(
@ -87,3 +139,41 @@ class ConvertToIfCaseStatementChain extends CorrectionProducer {
builder.write(code);
}
}
class _DefaultGroup extends _Group {
_DefaultGroup({
required super.statements,
});
}
sealed class _Group {
final List<Statement> statements;
_Group({
required this.statements,
});
}
/// Joined [Pattern]s, without `when`, before statements.
class _JoinedCaseGroup extends _Group {
final List<DartPattern> patterns;
_JoinedCaseGroup({
required this.patterns,
required super.statements,
});
}
/// A single [GuardedPattern] before statements.
class _SingleCaseGroup extends _Group {
final GuardedPattern guardedPattern;
_SingleCaseGroup({
required this.guardedPattern,
required super.statements,
});
}
extension on List<GuardedPattern> {
bool get hasWhen => any((e) => e.whenClause != null);
}

View file

@ -41,6 +41,80 @@ void f(Object? x) {
''');
}
Future<void> test_noDefault_hasWhen() async {
await resolveTestCode('''
void f(Object? x) {
switch (x) {
case int() when x > 0:
0;
case double():
1;
}
}
''');
await assertHasAssistAt('switch', '''
void f(Object? x) {
if (x case int() when x > 0) {
0;
} else if (x case double()) {
1;
}
}
''');
}
Future<void> test_noStatements() async {
await resolveTestCode('''
void f(Object? x) {
switch (x) {
case int():
0;
case double():
}
}
''');
await assertHasAssistAt('switch', '''
void f(Object? x) {
if (x case int()) {
0;
} else if (x case double()) {
}
}
''');
}
Future<void> test_sharedBody() async {
await resolveTestCode('''
void f(Object? x) {
switch (x) {
case int():
case double():
0;
}
}
''');
await assertHasAssistAt('switch', '''
void f(Object? x) {
if (x case int() || double()) {
0;
}
}
''');
}
Future<void> test_sharedBody_hasWhen() async {
await resolveTestCode('''
void f(Object? x) {
switch (x) {
case int() when x > 0:
case double():
0;
}
}
''');
await assertNoAssistAt('switch');
}
Future<void> test_withDefault() async {
await resolveTestCode('''
void f(Object? x) {
@ -66,4 +140,19 @@ void f(Object? x) {
}
''');
}
Future<void> test_withDefault_shared() async {
await resolveTestCode('''
void f(Object? x) {
switch (x) {
case int():
0;
case double():
default:
1;
}
}
''');
await assertNoAssistAt('switch');
}
}