+ convert_to_switch_expression assist

See: https://github.com/dart-lang/sdk/issues/50417

Initial work to support return conversions.  Arguments and assignments to come and will likely lead to some refactoring but there's enough here to benefit from some early feedback.

Thanks in advance! :D

Change-Id: Ic3d0349aa12d8c951e3afe0da3e00e2777480e38
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/286861
Commit-Queue: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
pq 2023-03-06 19:49:13 +00:00 committed by Commit Queue
parent bd02ce7a7f
commit 47cab3d803
5 changed files with 305 additions and 0 deletions

View file

@ -201,6 +201,11 @@ class DartAssistKind {
DartAssistKindPriority.DEFAULT,
'Convert to using super parameters',
);
static const CONVERT_TO_SWITCH_EXPRESSION = AssistKind(
'dart.assist.convert.switchExpression',
DartAssistKindPriority.DEFAULT,
'Convert to switch expression',
);
static const ENCAPSULATE_FIELD = AssistKind(
'dart.assist.encapsulateField',
DartAssistKindPriority.DEFAULT,

View file

@ -39,6 +39,7 @@ import 'package:analysis_server/src/services/correction/dart/convert_to_package_
import 'package:analysis_server/src/services/correction/dart/convert_to_relative_import.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_set_literal.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_super_parameters.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_switch_expression.dart';
import 'package:analysis_server/src/services/correction/dart/encapsulate_field.dart';
import 'package:analysis_server/src/services/correction/dart/exchange_operands.dart';
import 'package:analysis_server/src/services/correction/dart/flutter_convert_to_children.dart';
@ -120,6 +121,7 @@ class AssistProcessor extends BaseProcessor {
ConvertToSetLiteral.new,
ConvertToSingleQuotes.new,
ConvertToSuperParameters.new,
ConvertToSwitchExpression.new,
EncapsulateField.new,
ExchangeOperands.new,
FlutterConvertToChildren.new,

View file

@ -0,0 +1,99 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
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/dart/element/element.dart';
import 'package:analyzer/dart/element/type.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/range_factory.dart';
class ConvertToSwitchExpression extends CorrectionProducer {
@override
AssistKind get assistKind => DartAssistKind.CONVERT_TO_SWITCH_EXPRESSION;
@override
Future<void> compute(ChangeBuilder builder) async {
var node = this.node;
if (node is! SwitchStatement) return;
var expression = node.expression;
if (!expression.staticType.isExhaustive) return;
if (isReturnSwitch(node)) {
await convertReturnSwitchExpression(builder, node);
}
}
Future<void> convertReturnSwitchExpression(
ChangeBuilder builder, SwitchStatement node) async {
await builder.addDartFileEdit(file, (builder) {
builder.addSimpleInsertion(node.offset, 'return ');
builder.addSimpleInsertion(node.end, ';');
var memberCount = node.members.length;
for (var i = 0; i < memberCount; ++i) {
// Sure to be a SwitchPatternCase
var patternCase = node.members[i] as SwitchPatternCase;
builder.addDeletion(
range.startStart(patternCase.keyword, patternCase.guardedPattern));
var colonRange = range.entity(patternCase.colon);
builder.addSimpleReplacement(colonRange, ' =>');
var statement = patternCase.statements.first;
var hasComment = statement.beginToken.precedingComments != null;
if (statement is ReturnStatement) {
// Return expression is sure to be non-null
var deletion = !hasComment
? range.startOffsetEndOffset(range.offsetBy(colonRange, 1).offset,
statement.expression!.offset - 1)
: range.startStart(
statement.returnKeyword, statement.expression!);
builder.addDeletion(deletion);
}
if (!hasComment && statement is ExpressionStatement) {
var expression = statement.expression;
if (expression is ThrowExpression) {
var deletionRange = range.startOffsetEndOffset(
range.offsetBy(colonRange, 1).offset, statement.offset - 1);
builder.addDeletion(deletionRange);
}
}
var endToken = i < memberCount - 1 ? ',' : '';
builder.addSimpleReplacement(
range.entity(statement.endToken), endToken);
}
});
}
bool isReturnSwitch(SwitchStatement node) {
for (var member in node.members) {
if (member is! SwitchPatternCase) return false;
if (member.labels.isNotEmpty) return false;
var statements = member.statements;
if (statements.length != 1) return false;
var s = statements.first;
if (s is ReturnStatement && s.expression != null) continue;
if (s is! ExpressionStatement || s.expression is! ThrowExpression) {
return false;
}
}
return true;
}
}
extension on DartType? {
bool get isExhaustive {
var element = this?.element;
if (element is EnumElement) return true;
if (element is ClassElement) return element.isExhaustive;
if (element is MixinElement) return element.isExhaustive;
return false;
}
}

View file

@ -0,0 +1,197 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/src/services/correction/assist.dart';
import 'package:analyzer_plugin/utilities/assist/assist.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'assist_processor.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(ConvertToSwitchExpressionTest);
});
}
@reflectiveTest
class ConvertToSwitchExpressionTest extends AssistProcessorTest {
@override
AssistKind get kind => DartAssistKind.CONVERT_TO_SWITCH_EXPRESSION;
@FailingTest(reason: 'Not yet implemented')
Future<void> test_argument_switchExpression() async {
await resolveTestCode('''
enum Color {
red, blue, green, yellow
}
void f(Color color) {
switch (color) {
case Color.red:
print('red');
break;
case Color.blue:
print('blue');
break;
case Color.green:
throw 'Green is bad';
case Color.yellow:
print('yellow');
break;
}
}
''');
await assertHasAssistAt('(color)', '''
enum Color {
red, blue, green, yellow
}
void f(Color color) {
print(switch (color) {
Color.red => 'red',
Color.blue => 'blue',
Color.green => throw 'Green is bad',
Color.yellow => 'yellow'
});
}
''');
}
@FailingTest(reason: 'Not yet implemented')
Future<void> test_assignment_switchExpression() async {
await resolveTestCode('''
enum Color {
red, blue, green, yellow
}
String f(Color color) {
var name = '';
switch (color) {
case Color.red:
name = 'red';
break;
case Color.blue:
name = 'blue';
break;
case Color.green:
throw 'Green is bad';
case Color.yellow:
name = 'yellow';
break;
}
return name;
}
''');
await assertHasAssistAt('(color)', '''
enum Color {
red, blue, green, yellow
}
String f(Color color) {
var name = '';
name = switch (color) {
Color.red => 'red',
Color.blue => 'blue',
Color.green => throw 'Green is bad',
Color.yellow => 'yellow'
};
return name;
}
''');
}
Future<void> test_return_notExhaustive_noAssist() async {
await resolveTestCode('''
String f(int i) {
switch(i) {
case 1:
return 'one';
case 2:
return 'two';
}
return '';
}
''');
await assertNoAssistAt('switch');
}
Future<void> test_return_switchExpression() async {
await resolveTestCode('''
enum Color {
red, orange, yellow, green
}
String name(Color color) {
switch (color) {
case Color.red:
throw 'red!';
case Color.orange:
return 'orange';
case Color.green:
throw 'green';
case Color.yellow:
return 'yellow';
}
}
''');
await assertHasAssistAt('(color)', '''
enum Color {
red, orange, yellow, green
}
String name(Color color) {
return switch (color) {
Color.red => throw 'red!',
Color.orange => 'orange',
Color.green => throw 'green',
Color.yellow => 'yellow'
};
}
''');
}
Future<void> test_return_switchKeyword() async {
await resolveTestCode('''
enum Color {
red, orange, yellow, green
}
String name(Color color) {
switch (color) {
// Uh-oh.
case Color.red:
throw 'red!';
case Color.orange:
// Tangerine?
return 'orange';
case Color.green:
// Whoops.
throw 'green';
case Color.yellow:
return 'yellow';
}
}
''');
await assertHasAssistAt('switch', '''
enum Color {
red, orange, yellow, green
}
String name(Color color) {
return switch (color) {
// Uh-oh.
Color.red => throw 'red!',
Color.orange =>
// Tangerine?
'orange',
Color.green =>
// Whoops.
throw 'green',
Color.yellow => 'yellow'
};
}
''');
}
}

View file

@ -42,6 +42,7 @@ import 'convert_to_single_quoted_string_test.dart'
as convert_to_single_quoted_string;
import 'convert_to_spread_test.dart' as convert_to_spread;
import 'convert_to_super_parameters_test.dart' as convert_to_super_parameters;
import 'convert_to_switch_expression_test.dart' as convert_to_switch_expression;
import 'encapsulate_field_test.dart' as encapsulate_field;
import 'exchange_operands_test.dart' as exchange_operands;
import 'flutter_convert_to_children_test.dart' as flutter_convert_to_children;
@ -126,6 +127,7 @@ void main() {
convert_to_single_quoted_string.main();
convert_to_spread.main();
convert_to_super_parameters.main();
convert_to_switch_expression.main();
encapsulate_field.main();
exchange_operands.main();
flutter_convert_to_children.main();