Skip to content

Commit 1df157b

Browse files
kallentuCommit Queue
authored andcommitted
[analysis_server] Dot shorthands: Code completion for property accesses.
Code completion for property accesses. When typing `.`, code completion will suggest static members based on the context type at that location. Added unit tests with and without prefixes. I'll follow up with additional tests and changes for invocations. I wanted to make sure I wasn't missing anything here. Bug: #59836 Change-Id: Ib488bd02cdc66bacd6fa527b983a215f2e16b442 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/434144 Commit-Queue: Kallen Tu <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent 8b495a0 commit 1df157b

File tree

4 files changed

+244
-2
lines changed

4 files changed

+244
-2
lines changed

pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,26 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
926926
}
927927
}
928928

929+
@override
930+
void visitDotShorthandPropertyAccess(DotShorthandPropertyAccess node) {
931+
var contextType = _computeContextType(node);
932+
if (contextType == null) return;
933+
934+
var element = contextType.element3;
935+
if (element == null) return;
936+
937+
var parent = node.parent;
938+
var mustBeAssignable =
939+
parent is AssignmentExpression && node == parent.leftHandSide;
940+
var helper = declarationHelper(
941+
mustBeAssignable: mustBeAssignable,
942+
preferNonInvocation:
943+
element is InterfaceElement &&
944+
state.request.shouldSuggestTearOff(element),
945+
);
946+
helper.addStaticMembersOfElement(element);
947+
}
948+
929949
@override
930950
void visitDoubleLiteral(DoubleLiteral node) {
931951
_visitParentIfAtOrBeforeNode(node);
@@ -2277,10 +2297,19 @@ class InScopeCompletionPass extends SimpleAstVisitor<void> {
22772297
collector.completionLocation = 'PropertyAccess_propertyName';
22782298
var target = node.prefix;
22792299
var type = target.staticType;
2280-
if (type != null) {
2300+
if (type != null && type is! InvalidType) {
22812301
_forMemberAccess(node, type, onlySuper: target is SuperExpression);
22822302
} else {
2283-
var element = target.element;
2303+
Element? element;
2304+
if (target.name.isEmpty &&
2305+
featureSet.isEnabled(Feature.dot_shorthands)) {
2306+
var contextType = _computeContextType(node);
2307+
if (contextType == null) return;
2308+
element = contextType.element3;
2309+
} else {
2310+
element = target.element;
2311+
}
2312+
22842313
if (element != null) {
22852314
var parent = node.parent;
22862315
var mustBeAssignable =
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
// Copyright (c) 2025, 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:test_reflective_loader/test_reflective_loader.dart';
6+
7+
import '../../../../client/completion_driver_test.dart';
8+
9+
void main() {
10+
defineReflectiveSuite(() {
11+
defineReflectiveTests(DotShorthandPropertyAccessTest);
12+
defineReflectiveTests(DotShorthandPropertyAccessExperimentDisabledTest);
13+
});
14+
}
15+
16+
@reflectiveTest
17+
class DotShorthandPropertyAccessExperimentDisabledTest
18+
extends AbstractCompletionDriverTest
19+
with DotShorthandPropertyAccessExperimentDisabledTestCases {}
20+
21+
mixin DotShorthandPropertyAccessExperimentDisabledTestCases
22+
on AbstractCompletionDriverTest {
23+
@override
24+
List<String> get experiments => [];
25+
26+
Future<void> test_class() async {
27+
allowedIdentifiers = {'getter', 'notStatic'};
28+
await computeSuggestions('''
29+
class C {
30+
static C get getter => C();
31+
C get notStatic => C();
32+
}
33+
void f() {
34+
C c = .^
35+
}
36+
''');
37+
assertResponse(r'''
38+
suggestions
39+
''');
40+
}
41+
}
42+
43+
@reflectiveTest
44+
class DotShorthandPropertyAccessTest extends AbstractCompletionDriverTest
45+
with DotShorthandPropertyAccessTestCases {}
46+
47+
mixin DotShorthandPropertyAccessTestCases on AbstractCompletionDriverTest {
48+
Future<void> test_class() async {
49+
allowedIdentifiers = {'getter', 'notStatic'};
50+
await computeSuggestions('''
51+
class C {
52+
static C get getter => C();
53+
C get notStatic => C();
54+
}
55+
void f() {
56+
C c = .^
57+
}
58+
''');
59+
assertResponse(r'''
60+
suggestions
61+
getter
62+
kind: getter
63+
''');
64+
}
65+
66+
Future<void> test_class_chain() async {
67+
allowedIdentifiers = {'getter', 'anotherGetter', 'notStatic'};
68+
await computeSuggestions('''
69+
class C {
70+
static C get getter => C();
71+
static C get anotherGetter => C();
72+
C get notStatic => C();
73+
}
74+
void f() {
75+
C c = .anotherGetter.^
76+
}
77+
''');
78+
assertResponse(r'''
79+
suggestions
80+
notStatic
81+
kind: getter
82+
''');
83+
}
84+
85+
Future<void> test_class_chain_withPrefix() async {
86+
allowedIdentifiers = {'getter', 'anotherGetter', 'notStatic'};
87+
await computeSuggestions('''
88+
class C {
89+
static C get getter => C();
90+
static C get anotherGetter => C();
91+
C get notStatic => C();
92+
C get anotherNotStatic => C();
93+
}
94+
void f() {
95+
C c = .anotherGetter.no^
96+
}
97+
''');
98+
assertResponse(r'''
99+
replacement
100+
left: 2
101+
suggestions
102+
notStatic
103+
kind: getter
104+
''');
105+
}
106+
107+
Future<void> test_class_withPrefix() async {
108+
allowedIdentifiers = {'getter', 'anotherGetter', 'notStatic'};
109+
await computeSuggestions('''
110+
class C {
111+
static C get getter => C();
112+
static C get anotherGetter => C();
113+
C get notStatic => C();
114+
}
115+
void f() {
116+
C c = .a^
117+
}
118+
''');
119+
assertResponse(r'''
120+
replacement
121+
left: 1
122+
suggestions
123+
anotherGetter
124+
kind: getter
125+
''');
126+
}
127+
128+
Future<void> test_enum() async {
129+
allowedIdentifiers = {'red', 'blue', 'yellow'};
130+
await computeSuggestions('''
131+
enum E { red, blue, yellow }
132+
void f() {
133+
E e = .^
134+
}
135+
''');
136+
assertResponse(r'''
137+
suggestions
138+
blue
139+
kind: enumConstant
140+
red
141+
kind: enumConstant
142+
yellow
143+
kind: enumConstant
144+
''');
145+
}
146+
147+
Future<void> test_enum_withPrefix() async {
148+
allowedIdentifiers = {'red', 'blue', 'yellow', 'black'};
149+
await computeSuggestions('''
150+
enum E { red, blue, yellow, black }
151+
void f() {
152+
E e = .b^
153+
}
154+
''');
155+
assertResponse(r'''
156+
replacement
157+
left: 1
158+
suggestions
159+
black
160+
kind: enumConstant
161+
blue
162+
kind: enumConstant
163+
''');
164+
}
165+
166+
Future<void> test_extensionType() async {
167+
allowedIdentifiers = {'getter', 'notStatic'};
168+
await computeSuggestions('''
169+
extension type C(int x) {
170+
static C get getter => C(1);
171+
C get notStatic => C(1);
172+
}
173+
void f() {
174+
C c = .^
175+
}
176+
''');
177+
assertResponse(r'''
178+
suggestions
179+
getter
180+
kind: getter
181+
''');
182+
}
183+
184+
Future<void> test_extensionType_withPrefix() async {
185+
allowedIdentifiers = {'getter', 'anotherGetter', 'notStatic'};
186+
await computeSuggestions('''
187+
extension type C(int x) {
188+
static C get getter => C(1);
189+
static C get anotherGetter => C(1);
190+
C get notStatic => C(1);
191+
}
192+
void f() {
193+
C c = .a^
194+
}
195+
''');
196+
assertResponse(r'''
197+
replacement
198+
left: 1
199+
suggestions
200+
anotherGetter
201+
kind: getter
202+
''');
203+
}
204+
}

pkg/analysis_server/test/services/completion/dart/location/test_all.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import 'constructor_declaration_test.dart' as constructor_declaration;
2222
import 'constructor_invocation_test.dart' as constructor_invocation;
2323
import 'dart_doc_test.dart' as dart_doc;
2424
import 'directive_uri_test.dart' as directive_uri;
25+
import 'dot_shorthand_property_access_test.dart'
26+
as dot_shorthand_property_access;
2527
import 'enum_constant_test.dart' as enum_constant;
2628
import 'enum_declaration_test.dart' as enum_declaration;
2729
import 'extends_clause_test.dart' as extends_clause;
@@ -104,6 +106,7 @@ void main() {
104106
constructor_invocation.main();
105107
dart_doc.main();
106108
directive_uri.main();
109+
dot_shorthand_property_access.main();
107110
enum_constant.main();
108111
enum_declaration.main();
109112
extends_clause.main();

pkg/analyzer_plugin/lib/src/utilities/completion/optype.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,12 @@ class _OpTypeAstVisitor extends GeneralizingAstVisitor<void> {
604604
}
605605
}
606606

607+
@override
608+
void visitDotShorthandPropertyAccess(DotShorthandPropertyAccess node) {
609+
optype.completionLocation = 'DotShorthandPropertyAccess_propertyName';
610+
optype.includeReturnValueSuggestions = true;
611+
}
612+
607613
@override
608614
void visitEmptyStatement(EmptyStatement node) {
609615
optype.includeReturnValueSuggestions = true;

0 commit comments

Comments
 (0)