@@ -7,11 +7,19 @@ import 'package:analysis_server/src/services/correction/dart/abstract_producer.d
7
7
import 'package:analyzer/dart/ast/ast.dart' ;
8
8
import 'package:analyzer/dart/element/element.dart' ;
9
9
import 'package:analyzer/dart/element/type.dart' ;
10
+ import 'package:analyzer/source/source_range.dart' ;
11
+ import 'package:analyzer/src/dart/element/type_system.dart' ;
10
12
import 'package:analyzer_plugin/utilities/assist/assist.dart' ;
11
13
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart' ;
12
14
import 'package:analyzer_plugin/utilities/range_factory.dart' ;
13
15
14
16
class ConvertToSwitchExpression extends CorrectionProducer {
17
+ /// Local variable reference used in assignment switch expression generation.
18
+ LocalVariableElement ? writeElement;
19
+
20
+ /// Function reference used in argument switch expression generation.
21
+ FunctionElement ? functionElement;
22
+
15
23
@override
16
24
AssistKind get assistKind => DartAssistKind .CONVERT_TO_SWITCH_EXPRESSION ;
17
25
@@ -21,13 +29,120 @@ class ConvertToSwitchExpression extends CorrectionProducer {
21
29
if (node is ! SwitchStatement ) return ;
22
30
23
31
var expression = node.expression;
24
- if (! expression.staticType.isExhaustive ) return ;
32
+ if (! isAlwaysExhaustive ( expression.staticType) ) return ;
25
33
26
34
if (isReturnSwitch (node)) {
27
35
await convertReturnSwitchExpression (builder, node);
36
+ } else if (isAssignmentSwitch (node)) {
37
+ await convertAssignmentSwitchExpression (builder, node);
38
+ } else if (isArgumentSwitch (node)) {
39
+ await convertArgumentSwitchExpression (builder, node);
28
40
}
29
41
}
30
42
43
+ Future <void > convertArgumentSwitchExpression (
44
+ ChangeBuilder builder, SwitchStatement node) async {
45
+ await builder.addDartFileEdit (file, (builder) {
46
+ builder.addSimpleInsertion (node.offset, '${functionElement !.name }(' );
47
+
48
+ var memberCount = node.members.length;
49
+ for (var i = 0 ; i < memberCount; ++ i) {
50
+ // Sure to be a SwitchPatternCase
51
+ var patternCase = node.members[i] as SwitchPatternCase ;
52
+ builder.addDeletion (
53
+ range.startStart (patternCase.keyword, patternCase.guardedPattern));
54
+ var colonRange = range.entity (patternCase.colon);
55
+ builder.addSimpleReplacement (colonRange, ' => ' );
56
+
57
+ for (var statement in patternCase.statements) {
58
+ var hasComment = statement.beginToken.precedingComments != null ;
59
+
60
+ if (statement is ExpressionStatement ) {
61
+ var invocation = statement.expression;
62
+ if (invocation is MethodInvocation ) {
63
+ var deletion = ! hasComment
64
+ ? range.startOffsetEndOffset (
65
+ range.offsetBy (colonRange, 1 ).offset,
66
+ invocation.argumentList.leftParenthesis.end)
67
+ : range.startOffsetEndOffset (invocation.offset,
68
+ invocation.argumentList.leftParenthesis.end);
69
+ builder.addDeletion (deletion);
70
+ builder.addDeletion (
71
+ range.entity (invocation.argumentList.rightParenthesis));
72
+ }
73
+ }
74
+
75
+ if (! hasComment && statement.isThrowExpressionStatement) {
76
+ var deletionRange = range.startOffsetEndOffset (
77
+ range.offsetBy (colonRange, 1 ).offset, statement.offset);
78
+ builder.addDeletion (deletionRange);
79
+ }
80
+
81
+ if (statement is BreakStatement ) {
82
+ var deletion = getBreakRange (statement);
83
+ builder.addDeletion (deletion);
84
+ } else {
85
+ var endToken = i < memberCount - 1 ? ',' : '' ;
86
+ builder.addSimpleReplacement (
87
+ range.entity (statement.endToken), endToken);
88
+ }
89
+ }
90
+ }
91
+
92
+ builder.addSimpleInsertion (node.end, ');' );
93
+ });
94
+ }
95
+
96
+ Future <void > convertAssignmentSwitchExpression (
97
+ ChangeBuilder builder, SwitchStatement node) async {
98
+ await builder.addDartFileEdit (file, (builder) {
99
+ builder.addSimpleInsertion (node.offset, '${writeElement !.name } = ' );
100
+
101
+ var memberCount = node.members.length;
102
+ for (var i = 0 ; i < memberCount; ++ i) {
103
+ // todo(pq): extract shared replacement logic
104
+
105
+ // Sure to be a SwitchPatternCase
106
+ var patternCase = node.members[i] as SwitchPatternCase ;
107
+ builder.addDeletion (
108
+ range.startStart (patternCase.keyword, patternCase.guardedPattern));
109
+ var colonRange = range.entity (patternCase.colon);
110
+ builder.addSimpleReplacement (colonRange, ' =>' );
111
+
112
+ for (var statement in patternCase.statements) {
113
+ if (statement is ExpressionStatement ) {
114
+ var expression = statement.expression;
115
+ if (expression is AssignmentExpression ) {
116
+ var hasComment = statement.beginToken.precedingComments != null ;
117
+ var deletion = ! hasComment
118
+ ? range.startOffsetEndOffset (
119
+ range.offsetBy (colonRange, 1 ).offset,
120
+ expression.operator .end)
121
+ : range.startOffsetEndOffset (expression.beginToken.offset,
122
+ expression.rightHandSide.offset);
123
+ builder.addDeletion (deletion);
124
+ } else if (expression is ThrowExpression ) {
125
+ var deletionRange = range.startOffsetEndOffset (
126
+ range.offsetBy (colonRange, 1 ).offset, statement.offset - 1 );
127
+ builder.addDeletion (deletionRange);
128
+ }
129
+ }
130
+
131
+ if (statement is BreakStatement ) {
132
+ var deletion = getBreakRange (statement);
133
+ builder.addDeletion (deletion);
134
+ } else {
135
+ var endToken = i < memberCount - 1 ? ',' : '' ;
136
+ builder.addSimpleReplacement (
137
+ range.entity (statement.endToken), endToken);
138
+ }
139
+ }
140
+ }
141
+
142
+ builder.addSimpleInsertion (node.end, ';' );
143
+ });
144
+ }
145
+
31
146
Future <void > convertReturnSwitchExpression (
32
147
ChangeBuilder builder, SwitchStatement node) async {
33
148
await builder.addDartFileEdit (file, (builder) {
@@ -56,13 +171,10 @@ class ConvertToSwitchExpression extends CorrectionProducer {
56
171
builder.addDeletion (deletion);
57
172
}
58
173
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
- }
174
+ if (! hasComment && statement.isThrowExpressionStatement) {
175
+ var deletionRange = range.startOffsetEndOffset (
176
+ range.offsetBy (colonRange, 1 ).offset, statement.offset - 1 );
177
+ builder.addDeletion (deletionRange);
66
178
}
67
179
68
180
var endToken = i < memberCount - 1 ? ',' : '' ;
@@ -72,6 +184,82 @@ class ConvertToSwitchExpression extends CorrectionProducer {
72
184
});
73
185
}
74
186
187
+ SourceRange getBreakRange (BreakStatement statement) {
188
+ var previous = (statement.beginToken.precedingComments ??
189
+ statement.beginToken.previous)! ;
190
+ var deletion =
191
+ range.startOffsetEndOffset (previous.end, statement.endToken.end);
192
+ return deletion;
193
+ }
194
+
195
+ bool isAlwaysExhaustive (DartType ? type) {
196
+ if (type == null ) return false ;
197
+ return (typeSystem as TypeSystemImpl ).isAlwaysExhaustive (type);
198
+ }
199
+
200
+ // todo(pq): refactor the `is` checks to a single `getSwitchKind`
201
+ // that only looks at members once
202
+ // see: https://dart-review.googlesource.com/c/sdk/+/287904
203
+ bool isArgumentSwitch (SwitchStatement node) {
204
+ for (var member in node.members) {
205
+ if (member is ! SwitchPatternCase ) return false ;
206
+ if (member.labels.isNotEmpty) return false ;
207
+ var statements = member.statements;
208
+
209
+ if (statements.length == 1 &&
210
+ statements.first.isThrowExpressionStatement) {
211
+ continue ;
212
+ }
213
+
214
+ if (statements.length != 2 ) return false ;
215
+ if (statements[1 ] is ! BreakStatement ) return false ;
216
+ var s = statements[0 ];
217
+ if (s is ! ExpressionStatement ) return false ;
218
+ var expression = s.expression;
219
+ if (expression is ! MethodInvocation ) return false ;
220
+ var element = expression.methodName.staticElement;
221
+ if (element is ! FunctionElement ) return false ;
222
+ if (functionElement == null ) {
223
+ functionElement = element;
224
+ } else {
225
+ if (functionElement != element) return false ;
226
+ }
227
+ }
228
+
229
+ return functionElement != null ;
230
+ }
231
+
232
+ bool isAssignmentSwitch (SwitchStatement node) {
233
+ for (var member in node.members) {
234
+ if (member is ! SwitchPatternCase ) return false ;
235
+ if (member.labels.isNotEmpty) return false ;
236
+ var statements = member.statements;
237
+
238
+ if (statements.length == 1 &&
239
+ statements.first.isThrowExpressionStatement) {
240
+ continue ;
241
+ }
242
+
243
+ if (statements.length != 2 ) return false ;
244
+ if (statements[1 ] is ! BreakStatement ) return false ;
245
+ var s = statements[0 ];
246
+ if (s is ! ExpressionStatement ) return false ;
247
+ var expression = s.expression;
248
+ if (expression is ! AssignmentExpression ) return false ;
249
+ var leftHandSide = expression.leftHandSide;
250
+ if (leftHandSide is ! SimpleIdentifier ) return false ;
251
+ if (writeElement == null ) {
252
+ var element = leftHandSide.staticElement;
253
+ if (element is ! LocalVariableElement ) return false ;
254
+ writeElement = element;
255
+ } else {
256
+ if (writeElement != leftHandSide.staticElement) return false ;
257
+ }
258
+ }
259
+
260
+ return writeElement != null ;
261
+ }
262
+
75
263
bool isReturnSwitch (SwitchStatement node) {
76
264
for (var member in node.members) {
77
265
if (member is ! SwitchPatternCase ) return false ;
@@ -88,12 +276,10 @@ class ConvertToSwitchExpression extends CorrectionProducer {
88
276
}
89
277
}
90
278
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 ;
279
+ extension on Statement {
280
+ bool get isThrowExpressionStatement {
281
+ var self = this ;
282
+ if (self is ! ExpressionStatement ) return false ;
283
+ return self.expression is ThrowExpression ;
98
284
}
99
285
}
0 commit comments