Skip to content

Commit 47cab3d

Browse files
pqCommit Queue
authored and
Commit Queue
committed
+ convert_to_switch_expression assist
See: #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 <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent bd02ce7 commit 47cab3d

File tree

5 files changed

+305
-0
lines changed

5 files changed

+305
-0
lines changed

pkg/analysis_server/lib/src/services/correction/assist.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,11 @@ class DartAssistKind {
201201
DartAssistKindPriority.DEFAULT,
202202
'Convert to using super parameters',
203203
);
204+
static const CONVERT_TO_SWITCH_EXPRESSION = AssistKind(
205+
'dart.assist.convert.switchExpression',
206+
DartAssistKindPriority.DEFAULT,
207+
'Convert to switch expression',
208+
);
204209
static const ENCAPSULATE_FIELD = AssistKind(
205210
'dart.assist.encapsulateField',
206211
DartAssistKindPriority.DEFAULT,

pkg/analysis_server/lib/src/services/correction/assist_internal.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import 'package:analysis_server/src/services/correction/dart/convert_to_package_
3939
import 'package:analysis_server/src/services/correction/dart/convert_to_relative_import.dart';
4040
import 'package:analysis_server/src/services/correction/dart/convert_to_set_literal.dart';
4141
import 'package:analysis_server/src/services/correction/dart/convert_to_super_parameters.dart';
42+
import 'package:analysis_server/src/services/correction/dart/convert_to_switch_expression.dart';
4243
import 'package:analysis_server/src/services/correction/dart/encapsulate_field.dart';
4344
import 'package:analysis_server/src/services/correction/dart/exchange_operands.dart';
4445
import 'package:analysis_server/src/services/correction/dart/flutter_convert_to_children.dart';
@@ -120,6 +121,7 @@ class AssistProcessor extends BaseProcessor {
120121
ConvertToSetLiteral.new,
121122
ConvertToSingleQuotes.new,
122123
ConvertToSuperParameters.new,
124+
ConvertToSwitchExpression.new,
123125
EncapsulateField.new,
124126
ExchangeOperands.new,
125127
FlutterConvertToChildren.new,
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analysis_server/src/services/correction/assist.dart';
6+
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
7+
import 'package:analyzer/dart/ast/ast.dart';
8+
import 'package:analyzer/dart/element/element.dart';
9+
import 'package:analyzer/dart/element/type.dart';
10+
import 'package:analyzer_plugin/utilities/assist/assist.dart';
11+
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
12+
import 'package:analyzer_plugin/utilities/range_factory.dart';
13+
14+
class ConvertToSwitchExpression extends CorrectionProducer {
15+
@override
16+
AssistKind get assistKind => DartAssistKind.CONVERT_TO_SWITCH_EXPRESSION;
17+
18+
@override
19+
Future<void> compute(ChangeBuilder builder) async {
20+
var node = this.node;
21+
if (node is! SwitchStatement) return;
22+
23+
var expression = node.expression;
24+
if (!expression.staticType.isExhaustive) return;
25+
26+
if (isReturnSwitch(node)) {
27+
await convertReturnSwitchExpression(builder, node);
28+
}
29+
}
30+
31+
Future<void> convertReturnSwitchExpression(
32+
ChangeBuilder builder, SwitchStatement node) async {
33+
await builder.addDartFileEdit(file, (builder) {
34+
builder.addSimpleInsertion(node.offset, 'return ');
35+
builder.addSimpleInsertion(node.end, ';');
36+
37+
var memberCount = node.members.length;
38+
for (var i = 0; i < memberCount; ++i) {
39+
// Sure to be a SwitchPatternCase
40+
var patternCase = node.members[i] as SwitchPatternCase;
41+
builder.addDeletion(
42+
range.startStart(patternCase.keyword, patternCase.guardedPattern));
43+
var colonRange = range.entity(patternCase.colon);
44+
builder.addSimpleReplacement(colonRange, ' =>');
45+
46+
var statement = patternCase.statements.first;
47+
var hasComment = statement.beginToken.precedingComments != null;
48+
49+
if (statement is ReturnStatement) {
50+
// Return expression is sure to be non-null
51+
var deletion = !hasComment
52+
? range.startOffsetEndOffset(range.offsetBy(colonRange, 1).offset,
53+
statement.expression!.offset - 1)
54+
: range.startStart(
55+
statement.returnKeyword, statement.expression!);
56+
builder.addDeletion(deletion);
57+
}
58+
59+
if (!hasComment && statement is ExpressionStatement) {
60+
var expression = statement.expression;
61+
if (expression is ThrowExpression) {
62+
var deletionRange = range.startOffsetEndOffset(
63+
range.offsetBy(colonRange, 1).offset, statement.offset - 1);
64+
builder.addDeletion(deletionRange);
65+
}
66+
}
67+
68+
var endToken = i < memberCount - 1 ? ',' : '';
69+
builder.addSimpleReplacement(
70+
range.entity(statement.endToken), endToken);
71+
}
72+
});
73+
}
74+
75+
bool isReturnSwitch(SwitchStatement node) {
76+
for (var member in node.members) {
77+
if (member is! SwitchPatternCase) return false;
78+
if (member.labels.isNotEmpty) return false;
79+
var statements = member.statements;
80+
if (statements.length != 1) return false;
81+
var s = statements.first;
82+
if (s is ReturnStatement && s.expression != null) continue;
83+
if (s is! ExpressionStatement || s.expression is! ThrowExpression) {
84+
return false;
85+
}
86+
}
87+
return true;
88+
}
89+
}
90+
91+
extension on DartType? {
92+
bool get isExhaustive {
93+
var element = this?.element;
94+
if (element is EnumElement) return true;
95+
if (element is ClassElement) return element.isExhaustive;
96+
if (element is MixinElement) return element.isExhaustive;
97+
return false;
98+
}
99+
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analysis_server/src/services/correction/assist.dart';
6+
import 'package:analyzer_plugin/utilities/assist/assist.dart';
7+
import 'package:test_reflective_loader/test_reflective_loader.dart';
8+
9+
import 'assist_processor.dart';
10+
11+
void main() {
12+
defineReflectiveSuite(() {
13+
defineReflectiveTests(ConvertToSwitchExpressionTest);
14+
});
15+
}
16+
17+
@reflectiveTest
18+
class ConvertToSwitchExpressionTest extends AssistProcessorTest {
19+
@override
20+
AssistKind get kind => DartAssistKind.CONVERT_TO_SWITCH_EXPRESSION;
21+
22+
@FailingTest(reason: 'Not yet implemented')
23+
Future<void> test_argument_switchExpression() async {
24+
await resolveTestCode('''
25+
enum Color {
26+
red, blue, green, yellow
27+
}
28+
29+
void f(Color color) {
30+
switch (color) {
31+
case Color.red:
32+
print('red');
33+
break;
34+
case Color.blue:
35+
print('blue');
36+
break;
37+
case Color.green:
38+
throw 'Green is bad';
39+
case Color.yellow:
40+
print('yellow');
41+
break;
42+
}
43+
}
44+
''');
45+
await assertHasAssistAt('(color)', '''
46+
enum Color {
47+
red, blue, green, yellow
48+
}
49+
50+
void f(Color color) {
51+
print(switch (color) {
52+
Color.red => 'red',
53+
Color.blue => 'blue',
54+
Color.green => throw 'Green is bad',
55+
Color.yellow => 'yellow'
56+
});
57+
}
58+
''');
59+
}
60+
61+
@FailingTest(reason: 'Not yet implemented')
62+
Future<void> test_assignment_switchExpression() async {
63+
await resolveTestCode('''
64+
enum Color {
65+
red, blue, green, yellow
66+
}
67+
68+
String f(Color color) {
69+
var name = '';
70+
switch (color) {
71+
case Color.red:
72+
name = 'red';
73+
break;
74+
case Color.blue:
75+
name = 'blue';
76+
break;
77+
case Color.green:
78+
throw 'Green is bad';
79+
case Color.yellow:
80+
name = 'yellow';
81+
break;
82+
}
83+
return name;
84+
}
85+
''');
86+
await assertHasAssistAt('(color)', '''
87+
enum Color {
88+
red, blue, green, yellow
89+
}
90+
91+
String f(Color color) {
92+
var name = '';
93+
name = switch (color) {
94+
Color.red => 'red',
95+
Color.blue => 'blue',
96+
Color.green => throw 'Green is bad',
97+
Color.yellow => 'yellow'
98+
};
99+
return name;
100+
}
101+
''');
102+
}
103+
104+
Future<void> test_return_notExhaustive_noAssist() async {
105+
await resolveTestCode('''
106+
String f(int i) {
107+
switch(i) {
108+
case 1:
109+
return 'one';
110+
case 2:
111+
return 'two';
112+
}
113+
return '';
114+
}
115+
''');
116+
117+
await assertNoAssistAt('switch');
118+
}
119+
120+
Future<void> test_return_switchExpression() async {
121+
await resolveTestCode('''
122+
enum Color {
123+
red, orange, yellow, green
124+
}
125+
126+
String name(Color color) {
127+
switch (color) {
128+
case Color.red:
129+
throw 'red!';
130+
case Color.orange:
131+
return 'orange';
132+
case Color.green:
133+
throw 'green';
134+
case Color.yellow:
135+
return 'yellow';
136+
}
137+
}
138+
''');
139+
await assertHasAssistAt('(color)', '''
140+
enum Color {
141+
red, orange, yellow, green
142+
}
143+
144+
String name(Color color) {
145+
return switch (color) {
146+
Color.red => throw 'red!',
147+
Color.orange => 'orange',
148+
Color.green => throw 'green',
149+
Color.yellow => 'yellow'
150+
};
151+
}
152+
''');
153+
}
154+
155+
Future<void> test_return_switchKeyword() async {
156+
await resolveTestCode('''
157+
enum Color {
158+
red, orange, yellow, green
159+
}
160+
161+
String name(Color color) {
162+
switch (color) {
163+
// Uh-oh.
164+
case Color.red:
165+
throw 'red!';
166+
case Color.orange:
167+
// Tangerine?
168+
return 'orange';
169+
case Color.green:
170+
// Whoops.
171+
throw 'green';
172+
case Color.yellow:
173+
return 'yellow';
174+
}
175+
}
176+
''');
177+
await assertHasAssistAt('switch', '''
178+
enum Color {
179+
red, orange, yellow, green
180+
}
181+
182+
String name(Color color) {
183+
return switch (color) {
184+
// Uh-oh.
185+
Color.red => throw 'red!',
186+
Color.orange =>
187+
// Tangerine?
188+
'orange',
189+
Color.green =>
190+
// Whoops.
191+
throw 'green',
192+
Color.yellow => 'yellow'
193+
};
194+
}
195+
''');
196+
}
197+
}

pkg/analysis_server/test/src/services/correction/assist/test_all.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import 'convert_to_single_quoted_string_test.dart'
4242
as convert_to_single_quoted_string;
4343
import 'convert_to_spread_test.dart' as convert_to_spread;
4444
import 'convert_to_super_parameters_test.dart' as convert_to_super_parameters;
45+
import 'convert_to_switch_expression_test.dart' as convert_to_switch_expression;
4546
import 'encapsulate_field_test.dart' as encapsulate_field;
4647
import 'exchange_operands_test.dart' as exchange_operands;
4748
import 'flutter_convert_to_children_test.dart' as flutter_convert_to_children;
@@ -126,6 +127,7 @@ void main() {
126127
convert_to_single_quoted_string.main();
127128
convert_to_spread.main();
128129
convert_to_super_parameters.main();
130+
convert_to_switch_expression.main();
129131
encapsulate_field.main();
130132
exchange_operands.main();
131133
flutter_convert_to_children.main();

0 commit comments

Comments
 (0)