Skip to content

Commit 7b8de02

Browse files
DanTupCommit Queue
authored and
Commit Queue
committed
[analysis_server] Update "Go to Imports" to support enhanced parts
Instead of looking in a single root unit, we now look in each unit up the tree. Change-Id: I59b0692438dd73fce7be7790ddc24240761d72c3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/394200 Reviewed-by: Konstantin Shcheglov <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]>
1 parent 73e13d7 commit 7b8de02

File tree

3 files changed

+202
-52
lines changed

3 files changed

+202
-52
lines changed

pkg/analysis_server/lib/src/lsp/handlers/custom/handler_imports.dart

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@ import 'package:analysis_server/src/lsp/error_or.dart';
99
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
1010
import 'package:analysis_server/src/lsp/mapping.dart';
1111
import 'package:analyzer/dart/analysis/results.dart';
12-
import 'package:analyzer/dart/ast/ast.dart';
1312
import 'package:analyzer/dart/element/element2.dart';
13+
import 'package:analyzer/src/dart/ast/ast.dart';
1414
import 'package:analyzer/src/dart/ast/element_locator.dart';
15-
import 'package:analyzer/src/dart/ast/utilities.dart';
16-
17-
typedef _ImportRecord = ({CompilationUnit unit, ImportDirective directive});
15+
import 'package:analyzer/src/utilities/extensions/ast.dart';
16+
import 'package:analyzer/src/utilities/extensions/results.dart';
1817

1918
class ImportsHandler
2019
extends SharedMessageHandler<TextDocumentPositionParams, List<Location>?> {
@@ -60,7 +59,7 @@ class ImportsHandler
6059
);
6160

6261
return (library, unit, offset).mapResults((library, unit, offset) async {
63-
var node = NodeLocator(offset).searchWithin(unit.unit);
62+
var node = unit.unit.nodeCovering(offset: offset);
6463
if (node == null) {
6564
return success(null);
6665
}
@@ -89,29 +88,56 @@ class ImportsHandler
8988
element = enclosingElement;
9089
}
9190

92-
var locations = _getImportLocations(element, library, unit, prefix);
91+
var locations = _getImportLocations(library, unit, element, prefix);
9392

9493
return success(nullIfEmpty(locations));
9594
});
9695
}
9796

9897
/// Returns [Location]s for imports that import the given [element] into
99-
/// [unit].
98+
/// [unitResult].
10099
List<Location> _getImportLocations(
101-
Element2 element,
102100
ResolvedLibraryResult libraryResult,
103-
ResolvedUnitResult unit,
101+
ResolvedUnitResult? unitResult,
102+
Element2 element,
104103
String? prefix,
105104
) {
106105
var elementName = element.name3;
107106
if (elementName == null) {
108107
return [];
109108
}
110109

111-
var imports = _getImports(libraryResult);
112-
var results = <Location>[];
110+
// Search in each unit up the chain for related imports.
111+
while (unitResult is ResolvedUnitResult) {
112+
var results = _getImportsInUnit(
113+
unitResult.unit,
114+
element,
115+
prefix: prefix,
116+
elementName: elementName,
117+
);
118+
119+
// Stop searching in the unit where we find any matching imports.
120+
if (results.isNotEmpty) {
121+
return results;
122+
}
123+
124+
// Otherwise, we continue up the chain.
125+
unitResult = libraryResult.parentUnitOf(unitResult);
126+
}
127+
128+
return [];
129+
}
113130

114-
for (var (:unit, :directive) in imports) {
131+
/// Gets the locations of all imports that provide [element] with [prefix] in
132+
/// [unit].
133+
List<Location> _getImportsInUnit(
134+
CompilationUnit unit,
135+
Element2 element, {
136+
required String? prefix,
137+
required String elementName,
138+
}) {
139+
var results = <Location>[];
140+
for (var directive in unit.directives.whereType<ImportDirective>()) {
115141
var import = directive.libraryImport;
116142
if (import == null) continue;
117143

@@ -134,20 +160,6 @@ class ImportsHandler
134160
results.add(Location(uri: uri, range: range));
135161
}
136162
}
137-
138163
return results;
139164
}
140-
141-
List<_ImportRecord> _getImports(ResolvedLibraryResult libraryResult) {
142-
var imports = <_ImportRecord>[];
143-
144-
// TODO(dantup): With enhanced parts, we may need to look at more than
145-
// just the first fragment.
146-
var unit = libraryResult.units.first.unit;
147-
for (var directive in unit.directives.whereType<ImportDirective>()) {
148-
imports.add((unit: unit, directive: directive));
149-
}
150-
151-
return imports;
152-
}
153165
}

pkg/analysis_server/test/lsp/import_test.dart

Lines changed: 148 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ void main() {
1818

1919
@reflectiveTest
2020
class ImportTest extends AbstractLspAnalysisServerTest {
21+
/// A helper for creating files for multi-level-parts tests.
22+
({Uri uri, String filePath, TestCode code}) createFile(
23+
String filename,
24+
String content,
25+
) {
26+
assert(filename.endsWith('.dart'));
27+
var filePath = join(projectFolderPath, 'lib', filename);
28+
var code = TestCode.parse(content);
29+
newFile(filePath, code.code);
30+
return (uri: toUri(filePath), filePath: filePath, code: code);
31+
}
32+
2133
Future<void> test_constant() async {
2234
await _verifyGoToImports(
2335
TestCode.parse('''
@@ -180,6 +192,131 @@ a.A^? r;
180192
);
181193
}
182194

195+
Future<void> test_import_multiLevelParts() async {
196+
// Create a tree of files that all import 'dart:math' and ensure we find
197+
// only the import from the parent (not a grandparent, sibling, or child).
198+
//
199+
//
200+
// - root has import
201+
// - level1_other has import
202+
// - level1 has import, is the used reference
203+
// - level2_other has import
204+
// - level2 has reference
205+
// - level3_other has import
206+
207+
createFile('root.dart', '''
208+
import 'dart:math';
209+
part 'level1_other.dart';
210+
part 'level1.dart';
211+
''');
212+
213+
createFile('level1_other.dart', '''
214+
part of 'root.dart';
215+
import 'dart:math';
216+
''');
217+
218+
var level1 = createFile('level1.dart', '''
219+
part of 'root.dart';
220+
[!import 'dart:math';!]
221+
part 'level2_other.dart';
222+
part 'level2.dart';
223+
''');
224+
225+
createFile('level2_other.dart', '''
226+
part of 'level1.dart';
227+
import 'dart:math';
228+
''');
229+
230+
var level2 = createFile('level2.dart', '''
231+
part of 'level1.dart';
232+
part 'level3_other.dart';
233+
234+
Rando^m? r;
235+
''');
236+
237+
createFile('level3_other.dart', '''
238+
part of 'level2.dart';
239+
import 'dart:math';
240+
''');
241+
242+
await _verifyGoToImports(
243+
// Test the position in level2.
244+
level2.code,
245+
fileUri: level2.uri,
246+
// Expect only the nearest parent import (level1).
247+
expecting: (level1.uri, level1.code.ranges),
248+
);
249+
}
250+
251+
Future<void> test_import_multiLevelParts_findsInGrandParent() async {
252+
var root = createFile('root.dart', '''
253+
[!import 'dart:math';!]
254+
part 'level1.dart';
255+
''');
256+
257+
createFile('level1.dart', '''
258+
part of 'root.dart';
259+
part 'level2.dart';
260+
''');
261+
262+
var level2 = createFile('level2.dart', '''
263+
part of 'level1.dart';
264+
Rando^m? r;''');
265+
266+
await _verifyGoToImports(
267+
// Test the position in level2, expect reference in root.
268+
level2.code,
269+
fileUri: level2.uri,
270+
expecting: (root.uri, root.code.ranges),
271+
);
272+
}
273+
274+
Future<void> test_import_multiLevelParts_findsInParent() async {
275+
createFile('root.dart', '''
276+
part 'level1.dart';
277+
''');
278+
279+
var level1 = createFile('level1.dart', '''
280+
part of 'root.dart';
281+
[!import 'dart:math';!]
282+
part 'level2.dart';
283+
''');
284+
285+
var level2 = createFile('level2.dart', '''
286+
part of 'level1.dart';
287+
Rando^m? r;''');
288+
289+
await _verifyGoToImports(
290+
// Test the position in level2, expect reference in level1.
291+
level2.code,
292+
fileUri: level2.uri,
293+
expecting: (level1.uri, level1.code.ranges),
294+
);
295+
}
296+
297+
Future<void> test_import_multiLevelParts_findsInSelf() async {
298+
createFile('root.dart', '''
299+
part 'level1.dart';
300+
''');
301+
302+
createFile('level1.dart', '''
303+
part of 'root.dart';
304+
part 'level2.dart';
305+
''');
306+
307+
var level2 = createFile('level2.dart', '''
308+
part of 'level1.dart';
309+
[!import 'dart:math';!]
310+
Rando^m? r;''');
311+
312+
await _verifyGoToImports(
313+
// Test the position in level2, expect reference in same.
314+
level2.code,
315+
fileUri: level2.uri,
316+
expecting: (level2.uri, level2.code.ranges),
317+
);
318+
}
319+
183320
Future<void> test_import_part() async {
184321
var otherFileUri = Uri.file(join(projectFolderPath, 'lib', 'other.dart'));
185322
var main = TestCode.parse('''
@@ -195,7 +332,7 @@ part of '$mainFileUri';
195332
Rando^m? r;
196333
'''),
197334
fileUri: otherFileUri,
198-
expecting: [_Results(mainFileUri, main.ranges)],
335+
expecting: (mainFileUri, main.ranges),
199336
);
200337
}
201338

@@ -277,38 +414,23 @@ var a = 1.abs().ba^r();
277414
);
278415
}
279416

280-
void _expecting(List<Location>? res, List<_Results>? fileRanges) {
281-
List<Location>? expected;
282-
if (fileRanges != null && fileRanges.expand((r) => r.ranges).isNotEmpty) {
283-
expected = [
284-
for (final _Results(:uri, :ranges) in fileRanges)
285-
for (final range in ranges) Location(uri: uri, range: range.range),
286-
];
287-
}
288-
289-
expect(res, equals(expected));
290-
}
291-
292417
Future<void> _verifyGoToImports(
293418
TestCode code, {
294419
Uri? fileUri,
295-
List<_Results>? expecting,
420+
(Uri, List<TestCodeRange>)? expecting,
296421
}) async {
422+
expecting ??= (fileUri ?? mainFileUri, code.ranges);
423+
var (expectedUri, expectedRanges) = expecting;
424+
var expectedLocations = [
425+
for (var range in expectedRanges)
426+
Location(uri: expectedUri, range: range.range),
427+
];
428+
297429
newFile(fromUri(fileUri ?? mainFileUri), code.code);
298430
await initialize();
299431
await initialAnalysis;
300432
var res = await getImports(fileUri ?? mainFileUri, code.position.position);
301-
List<_Results>? results;
302-
if (expecting == null && code.ranges.isNotEmpty) {
303-
results = [_Results(fileUri ?? mainFileUri, code.ranges)];
304-
}
305-
_expecting(res, expecting ?? results);
306-
}
307-
}
308-
309-
extension type _Results._((Uri uri, List<TestCodeRange> ranges) _r) {
310-
_Results(Uri uri, List<TestCodeRange> ranges) : _r = (uri, ranges);
311433

312-
List<TestCodeRange> get ranges => _r.$2;
313-
Uri get uri => _r.$1;
434+
expect(res ?? [], expectedLocations);
435+
}
314436
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) 2024, 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:analyzer/dart/analysis/results.dart';
6+
7+
extension ResolvedLibraryResultExtension on ResolvedLibraryResult {
8+
/// Returns the parent unit (the unit that this is a `part of`) from this
9+
/// result.
10+
///
11+
/// Returns `null` if this result does not contain the parent unit.
12+
ResolvedUnitResult? parentUnitOf(ResolvedUnitResult unit) {
13+
var parentPath = unit.libraryFragment.enclosingFragment?.source.fullName;
14+
return parentPath != null ? unitWithPath(parentPath) : null;
15+
}
16+
}

0 commit comments

Comments
 (0)