Skip to content

Commit 1cf0dea

Browse files
kallentuCommit Queue
authored andcommitted
[analysis_server] Dot shorthands: Update RemoveTypeAnnotation fix and assist.
Similar to https://dart-review.googlesource.com/c/sdk/+/441320 (ReplaceWithVar fix and assist) Update the fix to remove the type annotation, but insert the declared type elsewhere so we can keep the context type for resolving a dot shorthand. Bug: #60957, #60994 Change-Id: Ib1af91090b49b10f4b866588c0837687bc901cfe Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/442283 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Kallen Tu <[email protected]>
1 parent 9a9166e commit 1cf0dea

File tree

3 files changed

+530
-11
lines changed

3 files changed

+530
-11
lines changed

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

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:analysis_server/src/utilities/extensions/ast.dart';
88
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
99
import 'package:analyzer/dart/ast/ast.dart';
1010
import 'package:analyzer/dart/ast/token.dart';
11+
import 'package:analyzer/src/utilities/dot_shorthands.dart';
1112
import 'package:analyzer/src/utilities/extensions/ast.dart';
1213
import 'package:analyzer_plugin/utilities/assist/assist.dart';
1314
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
@@ -58,6 +59,8 @@ class RemoveTypeAnnotation extends ParsedCorrectionProducer {
5859
if (node case TypeAnnotation(:var parent) when diagnostic != null) {
5960
if (parent is VariableDeclarationList) {
6061
return _removeFromDeclarationList(builder, parent);
62+
} else if (parent is DeclaredIdentifier) {
63+
return _removeFromDeclaredIdentifier(builder, parent);
6164
}
6265
return _removeTypeAnnotation(builder, node);
6366
}
@@ -94,36 +97,43 @@ class RemoveTypeAnnotation extends ParsedCorrectionProducer {
9497
return;
9598
}
9699

97-
String? typeArgumentsText;
98-
int? typeArgumentsOffset;
99-
if (type is NamedType) {
100+
String? insertionText;
101+
int? insertionOffset;
102+
if (isDotShorthand(initializer)) {
103+
// Inserts the type before the dot shorthand (e.g. `E.a` where type is
104+
// `E`) because we erase the required context type when we replace the
105+
// declared type with `var`.
106+
// TODO(kallentu): https://github.com/dart-lang/sdk/issues/61164
107+
insertionText = utils.getNodeText(type);
108+
insertionOffset = initializer.beginToken.offset;
109+
} else if (type is NamedType) {
100110
var typeArguments = type.typeArguments;
101111
if (typeArguments != null) {
102112
if (initializer is CascadeExpression) {
103113
initializer = initializer.target;
104114
}
105115
if (initializer is TypedLiteral) {
106116
if (initializer.typeArguments == null) {
107-
typeArgumentsText = utils.getNodeText(typeArguments);
117+
insertionText = utils.getNodeText(typeArguments);
108118
if (initializer is ListLiteral) {
109-
typeArgumentsOffset = initializer.leftBracket.offset;
119+
insertionOffset = initializer.leftBracket.offset;
110120
} else if (initializer is SetOrMapLiteral) {
111-
typeArgumentsOffset = initializer.leftBracket.offset;
121+
insertionOffset = initializer.leftBracket.offset;
112122
} else {
113123
throw StateError('Unhandled subclass of TypedLiteral');
114124
}
115125
}
116126
} else if (initializer is InstanceCreationExpression) {
117127
if (initializer.constructorName.type.typeArguments == null) {
118-
typeArgumentsText = utils.getNodeText(typeArguments);
119-
typeArgumentsOffset = initializer.constructorName.type.end;
128+
insertionText = utils.getNodeText(typeArguments);
129+
insertionOffset = initializer.constructorName.type.end;
120130
}
121131
}
122132
}
123133
}
124134
if (initializer is SetOrMapLiteral &&
125135
initializer.typeArguments == null &&
126-
typeArgumentsText == null) {
136+
insertionText == null) {
127137
// This is to prevent the fix from converting a valid map or set literal
128138
// into an ambiguous literal. We could apply this in more places
129139
// by examining the elements of the collection.
@@ -137,8 +147,8 @@ class RemoveTypeAnnotation extends ParsedCorrectionProducer {
137147
} else {
138148
builder.addSimpleReplacement(typeRange, '${Keyword.VAR.lexeme} ');
139149
}
140-
if (typeArgumentsText != null && typeArgumentsOffset != null) {
141-
builder.addSimpleInsertion(typeArgumentsOffset, typeArgumentsText);
150+
if (insertionText != null && insertionOffset != null) {
151+
builder.addSimpleInsertion(insertionOffset, insertionText);
142152
}
143153
});
144154
}
@@ -151,6 +161,21 @@ class RemoveTypeAnnotation extends ParsedCorrectionProducer {
151161
if (typeNode == null) {
152162
return;
153163
}
164+
165+
String? insertionText;
166+
int? insertionOffset;
167+
var parent = declaration.parent;
168+
if (parent is ForEachPartsWithDeclaration) {
169+
var iterable = parent.iterable;
170+
if (hasDependentDotShorthand(iterable) && iterable is TypedLiteral) {
171+
// If there's a dependent shorthand in the literal, we need to
172+
// insert explicit type arguments to ensure we have an appropriate
173+
// context type to resolve the dot shorthand.
174+
insertionText = '<${utils.getNodeText(typeNode)}>';
175+
insertionOffset = iterable.beginToken.offset;
176+
}
177+
}
178+
154179
var keyword = declaration.keyword;
155180
var variableName = declaration.name;
156181
await builder.addDartFileEdit(file, (builder) {
@@ -160,6 +185,10 @@ class RemoveTypeAnnotation extends ParsedCorrectionProducer {
160185
} else {
161186
builder.addSimpleReplacement(typeRange, '${Keyword.VAR.lexeme} ');
162187
}
188+
189+
if (insertionText != null && insertionOffset != null) {
190+
builder.addSimpleInsertion(insertionOffset, insertionText);
191+
}
163192
});
164193
}
165194

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

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,135 @@ class A {
5454
await assertNoAssist();
5555
}
5656

57+
Future<void> test_forEach_dotShorthands_functionType() async {
58+
await resolveTestCode('''
59+
enum E { a, b, c }
60+
void f() {
61+
for (E Fun^ction() e in [() => .a]) {
62+
print(e);
63+
}
64+
}
65+
''');
66+
await assertHasAssist('''
67+
enum E { a, b, c }
68+
void f() {
69+
for (var e in <E Function()>[() => .a]) {
70+
print(e);
71+
}
72+
}
73+
''');
74+
}
75+
76+
Future<void> test_forEach_dotShorthands_generic_nested() async {
77+
await resolveTestCode('''
78+
enum E { a, b, c }
79+
80+
T ff<T>(T t, E e) => t;
81+
82+
void f() {
83+
for (^E e in [ff(ff(.b, .b), .b)]) {
84+
print(e);
85+
}
86+
}
87+
''');
88+
await assertHasAssist('''
89+
enum E { a, b, c }
90+
91+
T ff<T>(T t, E e) => t;
92+
93+
void f() {
94+
for (var e in <E>[ff(ff(.b, .b), .b)]) {
95+
print(e);
96+
}
97+
}
98+
''');
99+
}
100+
101+
Future<void>
102+
test_forEach_dotShorthands_generic_nested_explicitTypeArguments() async {
103+
await resolveTestCode('''
104+
enum E { a, b, c }
105+
106+
T ff<T>(T t, E e) => t;
107+
108+
X fun<U, X>(U u, X x) => x;
109+
110+
void f() {
111+
for (^int e in [fun(ff<E>(.a, E.a), 2)]) {
112+
print(e);
113+
}
114+
}
115+
''');
116+
await assertHasAssist('''
117+
enum E { a, b, c }
118+
119+
T ff<T>(T t, E e) => t;
120+
121+
X fun<U, X>(U u, X x) => x;
122+
123+
void f() {
124+
for (var e in [fun(ff<E>(.a, E.a), 2)]) {
125+
print(e);
126+
}
127+
}
128+
''');
129+
}
130+
131+
Future<void> test_forEach_dotShorthands_list() async {
132+
await resolveTestCode('''
133+
enum E { a }
134+
void f() {
135+
for (^E e in [.a]) {
136+
print(e);
137+
}
138+
}
139+
''');
140+
await assertHasAssist('''
141+
enum E { a }
142+
void f() {
143+
for (var e in <E>[.a]) {
144+
print(e);
145+
}
146+
}
147+
''');
148+
}
149+
150+
Future<void> test_forEach_dotShorthands_set() async {
151+
await resolveTestCode('''
152+
enum E { a }
153+
void f() {
154+
for (^E e in {.a}) {
155+
print(e);
156+
}
157+
}
158+
''');
159+
await assertHasAssist('''
160+
enum E { a }
161+
void f() {
162+
for (var e in <E>{.a}) {
163+
print(e);
164+
}
165+
}
166+
''');
167+
}
168+
169+
Future<void> test_generic_instanceCreation_cascade_dotShorthand() async {
170+
await resolveTestCode('''
171+
enum E { a }
172+
Set f() {
173+
final Se^t<E> s = { .a }..addAll([]);
174+
return s;
175+
}
176+
''');
177+
await assertHasAssist('''
178+
enum E { a }
179+
Set f() {
180+
final s = <E>{ .a }..addAll([]);
181+
return s;
182+
}
183+
''');
184+
}
185+
57186
Future<void> test_generic_instanceCreation_withoutArguments() async {
58187
await resolveTestCode('''
59188
C<int> ^c = C();
@@ -74,6 +203,23 @@ var l = <int>[];
74203
''');
75204
}
76205

206+
Future<void> test_generic_listLiteral_dotShorthand() async {
207+
await resolveTestCode('''
208+
enum E { a, b }
209+
List f() {
210+
final Li^st<E> l = [.a, .b];
211+
return l;
212+
}
213+
''');
214+
await assertHasAssist('''
215+
enum E { a, b }
216+
List f() {
217+
final l = <E>[.a, .b];
218+
return l;
219+
}
220+
''');
221+
}
222+
77223
Future<void> test_generic_setLiteral_ambiguous() async {
78224
await resolveTestCode('''
79225
Set f() {
@@ -200,6 +346,61 @@ void f() {
200346
await assertNoAssist();
201347
}
202348

349+
Future<void> test_simple_dotShorthand_constructorInvocation() async {
350+
await resolveTestCode('''
351+
class E {}
352+
E f() {
353+
final ^E e = .new();
354+
return e;
355+
}
356+
''');
357+
await assertHasAssist('''
358+
class E {}
359+
E f() {
360+
final e = E.new();
361+
return e;
362+
}
363+
''');
364+
}
365+
366+
Future<void> test_simple_dotShorthand_methodInvocation() async {
367+
await resolveTestCode('''
368+
class E {
369+
static E method() => E();
370+
}
371+
E f() {
372+
final ^E e = .method();
373+
return e;
374+
}
375+
''');
376+
await assertHasAssist('''
377+
class E {
378+
static E method() => E();
379+
}
380+
E f() {
381+
final e = E.method();
382+
return e;
383+
}
384+
''');
385+
}
386+
387+
Future<void> test_simple_dotShorthand_propertyAccess() async {
388+
await resolveTestCode('''
389+
enum E { a }
390+
E f() {
391+
final ^E e = .a;
392+
return e;
393+
}
394+
''');
395+
await assertHasAssist('''
396+
enum E { a }
397+
E f() {
398+
final e = E.a;
399+
return e;
400+
}
401+
''');
402+
}
403+
203404
Future<void> test_topLevelVariable() async {
204405
await resolveTestCode('''
205406
^int V = 1;

0 commit comments

Comments
 (0)