Skip to content

Commit 25c72bc

Browse files
FMorschelCommit Queue
authored andcommitted
[DAS] Go to Imports option
This change is to add a command that can be used to see which imports are contributing a symbol in the document. It'll work similar to things like "Go to Super", but when there are multiple results it will show them in a peek window, like Find References does. Fixes: #56584 Change-Id: I70d369095a0500982818f0c8bea2651fe5f73431 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/387387 Commit-Queue: Brian Wilkerson <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent 73443c8 commit 25c72bc

File tree

12 files changed

+571
-28
lines changed

12 files changed

+571
-28
lines changed

pkg/analysis_server/analyzer_use_new_elements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ lib/src/lsp/completion_utils.dart
2222
lib/src/lsp/handlers/code_actions/dart.dart
2323
lib/src/lsp/handlers/commands/abstract_refactor.dart
2424
lib/src/lsp/handlers/custom/abstract_go_to.dart
25+
lib/src/lsp/handlers/custom/handler_imports.dart
2526
lib/src/lsp/handlers/custom/handler_augmentation.dart
2627
lib/src/lsp/handlers/custom/handler_augmented.dart
2728
lib/src/lsp/handlers/custom/handler_super.dart

pkg/analysis_server/lib/src/lsp/constants.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ abstract final class CustomMethods {
148148
'dart/textDocument/publishFlutterOutline',
149149
);
150150
static const super_ = Method('dart/textDocument/super');
151+
static const imports = Method('dart/textDocument/imports');
151152
static const dartTextDocumentContent = Method('dart/textDocumentContent');
152153
static const dartTextDocumentContentDidChange = Method(
153154
'dart/textDocumentContentDidChange',

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

Lines changed: 93 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:async';
6+
57
import 'package:analysis_server/lsp_protocol/protocol.dart' hide Element;
68
import 'package:analysis_server/src/lsp/error_or.dart';
79
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
810
import 'package:analysis_server/src/lsp/mapping.dart';
11+
import 'package:analyzer/dart/analysis/results.dart';
12+
import 'package:analyzer/dart/ast/ast.dart';
913
import 'package:analyzer/dart/element/element.dart';
1014
import 'package:analyzer/src/dart/ast/utilities.dart';
1115
import 'package:meta/meta.dart';
@@ -14,18 +18,53 @@ import 'package:meta/meta.dart';
1418
/// a location to navigate to a particular kind of related element, such as
1519
/// Super, Augmentation Target or Augmentation.
1620
abstract class AbstractGoToHandler
17-
extends SharedMessageHandler<TextDocumentPositionParams, Location?> {
21+
extends
22+
SharedMessageHandler<
23+
TextDocumentPositionParams,
24+
Either2<Location, List<Location>>?
25+
> {
1826
AbstractGoToHandler(super.server);
1927

2028
@override
2129
LspJsonHandler<TextDocumentPositionParams> get jsonHandler =>
2230
TextDocumentPositionParams.jsonHandler;
2331

32+
Location? elementToLocation(Element? element) {
33+
var targetElement = element?.nonSynthetic;
34+
if (targetElement == null) {
35+
return null;
36+
}
37+
var sourcePath = targetElement.declaration?.source?.fullName;
38+
if (sourcePath == null) {
39+
return null;
40+
}
41+
42+
// TODO(FMorschel): Remove this when migrating to the new element model.
43+
var locationLineInfo = server.getLineInfo(sourcePath);
44+
if (locationLineInfo == null) {
45+
return null;
46+
}
47+
48+
return Location(
49+
uri: uriConverter.toClientUri(sourcePath),
50+
range: toRange(
51+
locationLineInfo,
52+
targetElement.nameOffset,
53+
targetElement.nameLength,
54+
),
55+
);
56+
}
57+
2458
@protected
25-
Element? findRelatedElement(Element element);
59+
Either2<Location?, List<Location>> findRelatedLocations(
60+
Element element,
61+
ResolvedLibraryResult libraryResult,
62+
ResolvedUnitResult unit,
63+
String? prefix,
64+
);
2665

2766
@override
28-
Future<ErrorOr<Location?>> handle(
67+
Future<ErrorOr<Either2<Location, List<Location>>?>> handle(
2968
TextDocumentPositionParams params,
3069
MessageInfo message,
3170
CancellationToken token,
@@ -36,10 +75,24 @@ abstract class AbstractGoToHandler
3675

3776
var pos = params.position;
3877
var path = pathOfDoc(params.textDocument);
39-
var unit = await path.mapResult(requireResolvedUnit);
40-
var offset = unit.mapResultSync((unit) => toOffset(unit.lineInfo, pos));
78+
var library = await path.mapResult(requireResolvedLibrary);
79+
var unit = (path, library).mapResultsSync<ResolvedUnitResult>((
80+
path,
81+
library,
82+
) {
83+
var unit = library.unitWithPath(path);
84+
return unit != null
85+
? success(unit)
86+
: error(
87+
ErrorCodes.InternalError,
88+
'The library containing a path did not contain the path.',
89+
);
90+
});
91+
var offset = unit.mapResultSync(
92+
(unit) => toOffset(unit.unit.lineInfo, pos),
93+
);
4194

42-
return (unit, offset).mapResultsSync((unit, offset) {
95+
return (library, unit, offset).mapResults((library, unit, offset) async {
4396
var node = NodeLocator(offset).searchWithin(unit.unit);
4497
if (node == null) {
4598
return success(null);
@@ -57,26 +110,45 @@ abstract class AbstractGoToHandler
57110
return success(null);
58111
}
59112

60-
var targetElement = findRelatedElement(element)?.nonSynthetic;
61-
var sourcePath = targetElement?.declaration?.source?.fullName;
62-
63-
if (targetElement == null || sourcePath == null) {
64-
return success(null);
113+
String? prefix;
114+
if (node is NamedType) {
115+
prefix = node.importPrefix?.name.lexeme;
116+
} else if (node?.thisOrAncestorOfType<PrefixedIdentifier>()
117+
case PrefixedIdentifier identifier) {
118+
prefix = identifier.prefix.name;
119+
} else if (node is SimpleIdentifier) {
120+
if (node.parent case MethodInvocation(
121+
:var target,
122+
) when target is SimpleIdentifier?) {
123+
prefix = target?.toString();
124+
}
65125
}
66126

67-
var locationLineInfo = server.getLineInfo(sourcePath);
68-
if (locationLineInfo == null) {
69-
return success(null);
127+
var enclosingElement = element.enclosingElement3;
128+
if (enclosingElement is ExtensionElement) {
129+
element = enclosingElement;
70130
}
131+
var relatedElements = findRelatedLocations(
132+
element,
133+
library,
134+
unit,
135+
prefix,
136+
);
71137

72138
return success(
73-
Location(
74-
uri: uriConverter.toClientUri(sourcePath),
75-
range: toRange(
76-
locationLineInfo,
77-
targetElement.nameOffset,
78-
targetElement.nameLength,
79-
),
139+
relatedElements.map(
140+
(location) {
141+
if (location == null) {
142+
return null;
143+
}
144+
return Either2.t1(location);
145+
},
146+
(locations) {
147+
if (locations.isEmpty) {
148+
return null;
149+
}
150+
return Either2.t2(locations);
151+
},
80152
),
81153
);
82154
});

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'package:analysis_server/lsp_protocol/protocol.dart' hide Element;
66
import 'package:analysis_server/src/lsp/constants.dart';
77
import 'package:analysis_server/src/lsp/handlers/custom/abstract_go_to.dart';
8+
import 'package:analyzer/dart/analysis/results.dart';
89
import 'package:analyzer/dart/element/element.dart';
910
import 'package:analyzer/src/utilities/extensions/element.dart';
1011

@@ -18,7 +19,15 @@ class AugmentationHandler extends AbstractGoToHandler {
1819
bool get requiresTrustedCaller => false;
1920

2021
@override
21-
Element? findRelatedElement(Element element) {
22-
return element.augmentation;
22+
Either2<Location?, List<Location>> findRelatedLocations(
23+
Element element,
24+
ResolvedLibraryResult libraryResult,
25+
ResolvedUnitResult unit,
26+
String? prefix) {
27+
// Although the base class supports returning multiple elements, this
28+
// handler is documented to only return a single element.
29+
// Changing this to return a list could be a breaking change for
30+
// clients.
31+
return Either2.t1(elementToLocation(element.augmentation));
2332
}
2433
}

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'package:analysis_server/lsp_protocol/protocol.dart' hide Element;
66
import 'package:analysis_server/src/lsp/constants.dart';
77
import 'package:analysis_server/src/lsp/handlers/custom/abstract_go_to.dart';
8+
import 'package:analyzer/dart/analysis/results.dart';
89
import 'package:analyzer/dart/element/element.dart';
910

1011
class AugmentedHandler extends AbstractGoToHandler {
@@ -17,12 +18,20 @@ class AugmentedHandler extends AbstractGoToHandler {
1718
bool get requiresTrustedCaller => false;
1819

1920
@override
20-
Element? findRelatedElement(Element element) {
21-
return switch (element) {
21+
Either2<Location?, List<Location>> findRelatedLocations(
22+
Element element,
23+
ResolvedLibraryResult libraryResult,
24+
ResolvedUnitResult unit,
25+
String? prefix) {
26+
// Although the base class supports returning multiple elements, this
27+
// handler is documented to only return a single element.
28+
// Changing this to return a list could be a breaking change for
29+
// clients.
30+
return Either2.t1(elementToLocation(switch (element) {
2231
ExecutableElement element => element.augmentationTarget,
2332
InstanceElement element => element.augmentationTarget,
2433
PropertyInducingElement element => element.augmentationTarget,
2534
_ => null,
26-
};
35+
}));
2736
}
2837
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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:analysis_server/lsp_protocol/protocol.dart' hide Element;
6+
import 'package:analysis_server/src/lsp/constants.dart';
7+
import 'package:analysis_server/src/lsp/handlers/custom/abstract_go_to.dart';
8+
import 'package:analysis_server/src/lsp/mapping.dart';
9+
import 'package:analyzer/dart/analysis/results.dart';
10+
import 'package:analyzer/dart/ast/ast.dart';
11+
import 'package:analyzer/dart/element/element.dart';
12+
13+
typedef _ImportRecord =
14+
({ImportDirective directive, LibraryImportElement import});
15+
16+
class ImportsHandler extends AbstractGoToHandler {
17+
ImportsHandler(super.server);
18+
19+
@override
20+
Method get handlesMessage => CustomMethods.imports;
21+
22+
@override
23+
bool get requiresTrustedCaller => false;
24+
25+
/// Returns the import directives that import the given [element].
26+
/// Although the base class supports returning a single element?, this
27+
/// handler is documented to return a list of elements.
28+
/// If no element is found, an empty list is returned.
29+
/// Changing this to return a single element could be a breaking change for
30+
/// clients.
31+
@override
32+
Either2<Location?, List<Location>> findRelatedLocations(
33+
Element element,
34+
ResolvedLibraryResult libraryResult,
35+
ResolvedUnitResult unit,
36+
String? prefix,
37+
) {
38+
var elementName = element.name;
39+
if (elementName == null) {
40+
return Either2.t1(null);
41+
}
42+
43+
var imports = _getImports(libraryResult);
44+
45+
var directives = <ImportDirective>[];
46+
for (var (:directive, :import) in imports) {
47+
Element? namespaceElement;
48+
49+
if (prefix == null) {
50+
namespaceElement = import.namespace.get(elementName);
51+
} else {
52+
namespaceElement = import.namespace.getPrefixed(prefix, elementName);
53+
}
54+
55+
if (element is MultiplyDefinedElement) {
56+
if (element.conflictingElements.contains(namespaceElement)) {
57+
directives.add(directive);
58+
}
59+
} else if (namespaceElement == element) {
60+
directives.add(directive);
61+
}
62+
}
63+
return Either2.t2(directives.map(_importToLocation).nonNulls.toList());
64+
}
65+
66+
List<_ImportRecord> _getImports(ResolvedLibraryResult libraryResult) {
67+
// TODO(dantup): Confirm that `units.first` is always the containing
68+
// library.
69+
var containingUnit = libraryResult.units.firstOrNull?.unit;
70+
if (containingUnit == null) {
71+
return const [];
72+
}
73+
var directives = containingUnit.directives.whereType<ImportDirective>();
74+
var imports = <_ImportRecord>[];
75+
for (var directive in directives) {
76+
if (directive.element case var import?) {
77+
imports.add((directive: directive, import: import));
78+
}
79+
}
80+
return imports;
81+
}
82+
83+
Location? _importToLocation(ImportDirective directive) {
84+
var sourcePath = directive.element?.declaration.source?.fullName;
85+
if (sourcePath == null) {
86+
return null;
87+
}
88+
89+
// TODO(FMorschel): Remove this when migrating to the new element model.
90+
var locationLineInfo = server.getLineInfo(sourcePath);
91+
if (locationLineInfo == null) {
92+
return null;
93+
}
94+
95+
return Location(
96+
uri: uriConverter.toClientUri(sourcePath),
97+
range: toRange(locationLineInfo, directive.offset, directive.length),
98+
);
99+
}
100+
}

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'package:analysis_server/lsp_protocol/protocol.dart' hide Element;
66
import 'package:analysis_server/src/lsp/constants.dart';
77
import 'package:analysis_server/src/lsp/handlers/custom/abstract_go_to.dart';
8+
import 'package:analyzer/dart/analysis/results.dart';
89
import 'package:analyzer/dart/element/element.dart';
910
import 'package:analyzer/src/dart/analysis/session.dart';
1011
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
@@ -20,8 +21,17 @@ class SuperHandler extends AbstractGoToHandler {
2021
bool get requiresTrustedCaller => false;
2122

2223
@override
23-
Element? findRelatedElement(Element element) {
24-
return _SuperComputer().computeSuper(element);
24+
Either2<Location?, List<Location>> findRelatedLocations(
25+
Element element,
26+
ResolvedLibraryResult libraryResult,
27+
ResolvedUnitResult unit,
28+
String? prefix) {
29+
// Although the base class supports returning multiple elements, this
30+
// handler is documented to only return a single element.
31+
// Changing this to return a list could be a breaking change for
32+
// clients.
33+
return Either2.t1(
34+
elementToLocation(_SuperComputer().computeSuper(element)));
2535
}
2636
}
2737

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:analysis_server/src/lsp/handlers/custom/handler_augmented.dart';
1313
import 'package:analysis_server/src/lsp/handlers/custom/handler_connect_to_dtd.dart';
1414
import 'package:analysis_server/src/lsp/handlers/custom/handler_dart_text_document_content_provider.dart';
1515
import 'package:analysis_server/src/lsp/handlers/custom/handler_diagnostic_server.dart';
16+
import 'package:analysis_server/src/lsp/handlers/custom/handler_imports.dart';
1617
import 'package:analysis_server/src/lsp/handlers/custom/handler_reanalyze.dart';
1718
import 'package:analysis_server/src/lsp/handlers/custom/handler_super.dart';
1819
import 'package:analysis_server/src/lsp/handlers/handler_call_hierarchy.dart';
@@ -129,6 +130,7 @@ class InitializedStateMessageHandler extends ServerStateMessageHandler {
129130
FormatRangeHandler.new,
130131
FormattingHandler.new,
131132
HoverHandler.new,
133+
ImportsHandler.new,
132134
ImplementationHandler.new,
133135
IncomingCallHierarchyHandler.new,
134136
OutgoingCallHierarchyHandler.new,

0 commit comments

Comments
 (0)