Skip to content

Commit bf7f517

Browse files
DanTupCommit Queue
authored and
Commit Queue
committed
[analysis_server] Treat field formal parameters and fields equally when finding references in LSP
This changes LSP's Find References to treat fields and field formal parameters as a single entity (matching what Document Highlights (Occurences) and Rename refactor does). It does this in one direction by resolving the initial element to a field if it's a FieldFormalParameter, and in the other by including FieldFormalParameters when collecting the set of elements in the hierarchy to find references to. Fixes Dart-Code/Dart-Code#4868 Change-Id: I33626a8d58f35d0c3bda574078f32bf98f4bc87c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/364120 Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]>
1 parent 95f90f5 commit bf7f517

File tree

5 files changed

+166
-45
lines changed

5 files changed

+166
-45
lines changed

pkg/analysis_server/lib/src/lsp/handlers/handler_references.dart

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import 'package:analysis_server/src/lsp/error_or.dart';
77
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
88
import 'package:analysis_server/src/lsp/mapping.dart';
99
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
10-
import 'package:analysis_server/src/protocol_server.dart' show NavigationTarget;
1110
import 'package:analysis_server/src/search/element_references.dart';
1211
import 'package:analysis_server/src/services/search/search_engine.dart'
1312
show SearchMatch;
@@ -16,8 +15,6 @@ import 'package:analyzer/dart/ast/ast.dart';
1615
import 'package:analyzer/dart/element/element.dart';
1716
import 'package:analyzer/src/dart/ast/utilities.dart';
1817
import 'package:analyzer/src/util/performance/operation_performance.dart';
19-
import 'package:analyzer_plugin/src/utilities/navigation/navigation.dart';
20-
import 'package:analyzer_plugin/utilities/navigation/navigation_dart.dart';
2118

2219
typedef StaticOptions = Either2<bool, ReferenceOptions>;
2320

@@ -48,19 +45,20 @@ class ReferencesHandler
4845
_getReferences(unit, offset, params, performance)));
4946
}
5047

51-
List<Location> _getDeclarations(ParsedUnitResult result, int offset) {
52-
var collector = NavigationCollectorImpl();
53-
computeDartNavigation(
54-
server.resourceProvider, collector, result, offset, 0);
55-
56-
return convert(collector.targets, (NavigationTarget target) {
57-
var targetFilePath = collector.files[target.fileIndex];
58-
var targetFileUri = uriConverter.toClientUri(targetFilePath);
59-
var lineInfo = server.getLineInfo(targetFilePath);
60-
return lineInfo != null
61-
? navigationTargetToLocation(targetFileUri, target, lineInfo)
62-
: null;
63-
}).nonNulls.toList();
48+
List<Location> _getDeclarations(Element element) {
49+
element = element.nonSynthetic;
50+
var unitElement = element.thisOrAncestorOfType<CompilationUnitElement>();
51+
if (unitElement == null) {
52+
return [];
53+
}
54+
55+
return [
56+
Location(
57+
uri: uriConverter.toClientUri(unitElement.source.fullName),
58+
range: toRange(
59+
unitElement.lineInfo, element.nameOffset, element.nameLength),
60+
)
61+
];
6462
}
6563

6664
Future<ErrorOr<List<Location>?>> _getReferences(
@@ -72,6 +70,7 @@ class ReferencesHandler
7270
node = _getReferenceTargetNode(node);
7371

7472
var element = switch (server.getElementOfNode(node)) {
73+
FieldFormalParameterElement(:var field?) => field,
7574
PropertyAccessorElement(:var variable2?) => variable2,
7675
(var element) => element,
7776
};
@@ -106,9 +105,9 @@ class ReferencesHandler
106105
'convert', (_) => convert(results, toLocation).nonNulls.toList());
107106

108107
if (params.context.includeDeclaration == true) {
109-
// Also include the definition for the symbol at this location.
108+
// Also include the definition for the resolved element.
110109
referenceResults.addAll(performance.run(
111-
'_getDeclarations', (_) => _getDeclarations(result, offset)));
110+
'_getDeclarations', (_) => _getDeclarations(element)));
112111
}
113112

114113
return success(referenceResults);

pkg/analysis_server/lib/src/search/element_references.dart

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,25 @@ class ElementReferencesComputer {
6868
///
6969
/// Otherwise, only references to [element] should be searched.
7070
Future<Iterable<Element>> _getRefElements(
71-
Element element, OperationPerformanceImpl performance) {
71+
Element element, OperationPerformanceImpl performance) async {
7272
if (element is ParameterElement && element.isNamed) {
73-
return performance.runAsync('getHierarchyNamedParameters',
73+
return await performance.runAsync('getHierarchyNamedParameters',
7474
(_) => getHierarchyNamedParameters(searchEngine, element));
7575
}
7676
if (element is ClassMemberElement) {
77-
return performance.runAsync(
78-
'getHierarchyMembers',
79-
(performance) => getHierarchyMembers(searchEngine, element,
80-
performance: performance));
77+
var (members, parameters) = await performance.runAsync(
78+
'getHierarchyMembers',
79+
(performance) => getHierarchyMembersAndParameters(
80+
searchEngine,
81+
element,
82+
performance: performance,
83+
includeParametersForFields: true,
84+
),
85+
);
86+
87+
return {...members, ...parameters};
8188
}
82-
return Future.value([element]);
89+
return [element];
8390
}
8491

8592
static SearchResult toResult(SearchMatch match) {

pkg/analysis_server/lib/src/services/refactoring/legacy/convert_getter_to_method.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class ConvertGetterToMethodRefactoringImpl extends RefactoringImpl
5959
(field.enclosingElement is InterfaceElement ||
6060
field.enclosingElement is ExtensionElement)) {
6161
var elements = await getHierarchyMembers(searchEngine, field);
62-
await Future.forEach(elements, (ClassMemberElement member) async {
62+
await Future.forEach(elements, (Element member) async {
6363
if (member is FieldElement) {
6464
var getter = member.getter;
6565
if (getter != null && !getter.isSynthetic) {

pkg/analysis_server/lib/src/services/search/hierarchy.dart

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -80,23 +80,49 @@ List<Element> getExtensionMembers(ExtensionElement extension, [String? name]) {
8080
return members;
8181
}
8282

83-
/// Return all implementations of the given [member], its superclasses, and
84-
/// their subclasses.
83+
/// Return all implementations of the given [member], including in its
84+
/// superclasses and their subclasses.
85+
///
86+
/// If [includeParametersForFields] is true and [member] is a [FieldElement],
87+
/// any [FieldFormalParameterElement]s for the member will also be provided
88+
/// (otherwise, the parameter set will be empty in the result).
8589
Future<Set<ClassMemberElement>> getHierarchyMembers(
86-
SearchEngine searchEngine, ClassMemberElement member,
87-
{OperationPerformanceImpl? performance}) async {
90+
SearchEngine searchEngine,
91+
ClassMemberElement member, {
92+
OperationPerformanceImpl? performance,
93+
}) async {
94+
var (members, _) = await getHierarchyMembersAndParameters(
95+
searchEngine, member,
96+
performance: performance);
97+
return members;
98+
}
99+
100+
/// Return all implementations of the given [member], including in its
101+
/// superclasses and their subclasses.
102+
///
103+
/// If [includeParametersForFields] is true and [member] is a [FieldElement],
104+
/// any [FieldFormalParameterElement]s for the member will also be provided
105+
/// (otherwise, the parameter set will be empty in the result).
106+
Future<(Set<ClassMemberElement>, Set<ParameterElement>)>
107+
getHierarchyMembersAndParameters(
108+
SearchEngine searchEngine,
109+
ClassMemberElement member, {
110+
OperationPerformanceImpl? performance,
111+
bool includeParametersForFields = false,
112+
}) async {
88113
performance ??= OperationPerformanceImpl('<root>');
89-
Set<ClassMemberElement> result = HashSet<ClassMemberElement>();
114+
Set<ClassMemberElement> members = HashSet<ClassMemberElement>();
115+
Set<ParameterElement> parameters = HashSet<ParameterElement>();
90116
// extension member
91117
var enclosingElement = member.enclosingElement;
92118
if (enclosingElement is ExtensionElement) {
93-
result.add(member);
94-
return Future.value(result);
119+
members.add(member);
120+
return (members, parameters);
95121
}
96122
// static elements
97123
if (member.isStatic || member is ConstructorElement) {
98-
result.add(member);
99-
return result;
124+
members.add(member);
125+
return (members, parameters);
100126
}
101127
// method, field, etc
102128
if (enclosingElement is InterfaceElement) {
@@ -133,13 +159,25 @@ Future<Set<ClassMemberElement>> getHierarchyMembers(
133159
var subClassMembers = getChildren(subClass, name);
134160
for (var member in subClassMembers) {
135161
if (member is ClassMemberElement) {
136-
result.add(member);
162+
members.add(member);
163+
}
164+
}
165+
166+
if (includeParametersForFields && member is FieldElement) {
167+
for (var constructor in subClass.constructors) {
168+
for (var parameter in constructor.parameters) {
169+
if (parameter is FieldFormalParameterElement &&
170+
parameter.field == member) {
171+
parameters.add(parameter);
172+
}
173+
}
137174
}
138175
}
139176
}
177+
return (members, parameters);
140178
}
141179

142-
return result;
180+
return (members, parameters);
143181
}
144182

145183
/// If the [element] is a named parameter in a [MethodElement], return all

pkg/analysis_server/test/lsp/references_test.dart

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,19 @@ void f() {
9696
}
9797

9898
Future<void> test_field_decalaration_initializingFormal() async {
99-
// References on the field should find both the initializing formal and the
100-
// reference to the getter.
99+
// References on the field should find the initializing formal, the
100+
// reference to the getter and the constructor argument.
101101
var content = '''
102102
class AAA {
103103
final String? aa^a;
104104
const AAA({this./*[0*/aaa/*0]*/});
105105
}
106106
107-
final a = AAA(aaa: '')./*[1*/aaa/*1]*/;
107+
class BBB extends AAA {
108+
BBB({super./*[1*/aaa/*1]*/});
109+
}
110+
111+
final a = AAA(/*[2*/aaa/*2]*/: '')./*[3*/aaa/*3]*/;
108112
''';
109113

110114
await _checkRanges(content);
@@ -209,15 +213,83 @@ imp^ort 'dart:async' as async;
209213
await _checkRanges(content);
210214
}
211215

212-
Future<void> test_initializingFormals() async {
213-
// References on "this.aaa" should only find the matching named argument.
216+
Future<void> test_initializingFormal_argument_withDeclaration() async {
217+
// Find references on an initializing formal argument should include
218+
// all references to the field too.
219+
var content = '''
220+
class AAA {
221+
String? /*[0*/aaa/*0]*/;
222+
AAA({this./*[1*/aaa/*1]*/});
223+
}
224+
225+
void f() {
226+
final a = AAA(/*[2*/a^aa/*2]*/: '');
227+
var x = a./*[3*/aaa/*3]*/;
228+
a./*[4*/aaa/*4]*/ = '';
229+
}
230+
''';
231+
232+
await _checkRanges(content, includeDeclarations: true);
233+
}
234+
235+
Future<void> test_initializingFormal_argument_withoutDeclaration() async {
236+
// Find references on an initializing formal argument should include
237+
// all references to the field too. The field is not included
238+
// because we didn't request the declaration.
214239
var content = '''
215240
class AAA {
216-
final String? aaa;
217-
const AAA({this.aa^a});
241+
String? aaa;
242+
AAA({this./*[0*/aaa/*0]*/});
243+
}
244+
245+
void f() {
246+
final a = AAA(/*[1*/a^aa/*1]*/: '');
247+
var x = a./*[2*/aaa/*2]*/;
248+
a./*[3*/aaa/*3]*/ = '';
218249
}
250+
''';
251+
252+
await _checkRanges(content);
253+
}
219254

220-
final a = AAA([!aaa!]: '').aaa;
255+
Future<void> test_initializingFormal_parameter_withDeclaration() async {
256+
// Find references on an initializing formal parameter should include
257+
// all references to the field too.
258+
var content = '''
259+
class AAA {
260+
String? /*[0*/aaa/*0]*/;
261+
AAA({this./*[1*/aa^a/*1]*/});
262+
}
263+
264+
void f() {
265+
final a = AAA(/*[2*/aaa/*2]*/: '');
266+
var x = a./*[3*/aaa/*3]*/;
267+
a./*[4*/aaa/*4]*/ = '';
268+
}
269+
''';
270+
271+
await _checkRanges(content, includeDeclarations: true);
272+
}
273+
274+
Future<void> test_initializingFormal_parameter_withoutDeclaration() async {
275+
// Find references on an initializing formal parameter should include
276+
// all references to the field too. The field is not included
277+
// because we didn't request the declaration.
278+
var content = '''
279+
class AAA {
280+
String? aaa;
281+
AAA({this./*[0*/aa^a/*0]*/});
282+
}
283+
284+
class BBB extends AAA {
285+
BBB({super./*[1*/aaa/*1]*/});
286+
}
287+
288+
void f() {
289+
final a = AAA(/*[2*/aaa/*2]*/: '');
290+
var x = a./*[3*/aaa/*3]*/;
291+
a./*[4*/aaa/*4]*/ = '';
292+
}
221293
''';
222294

223295
await _checkRanges(content);
@@ -410,6 +482,11 @@ class Aaa^<T> {}
410482
Location(uri: otherFileUri, range: range.range),
411483
];
412484

485+
// Checking sets produces a better failure message than lists
486+
// (it'll show which item is missing instead of just saying
487+
// the lengths are different), so check that first.
488+
expect(res.toSet(), expected.toSet());
489+
// But also check the list in case there were unexpected duplicates.
413490
expect(res, unorderedEquals(expected));
414491
}
415492
}

0 commit comments

Comments
 (0)