|
3 | 3 | // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
|
5 | 5 | import 'package:analysis_server/lsp_protocol/protocol.dart' hide Element; |
| 6 | +import 'package:analysis_server/src/collections.dart'; |
6 | 7 | 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/error_or.dart'; |
| 9 | +import 'package:analysis_server/src/lsp/handlers/handlers.dart'; |
8 | 10 | import 'package:analysis_server/src/lsp/mapping.dart'; |
9 | 11 | import 'package:analyzer/dart/analysis/results.dart'; |
10 | 12 | import 'package:analyzer/dart/ast/ast.dart'; |
11 | | -import 'package:analyzer/dart/element/element.dart'; |
| 13 | +import 'package:analyzer/dart/element/element2.dart'; |
| 14 | +import 'package:analyzer/src/dart/ast/element_locator.dart'; |
| 15 | +import 'package:analyzer/src/dart/ast/utilities.dart'; |
12 | 16 |
|
13 | | -typedef _ImportRecord = |
14 | | - ({ImportDirective directive, LibraryImportElement import}); |
| 17 | +typedef _ImportRecord = ({CompilationUnit unit, ImportDirective directive}); |
15 | 18 |
|
16 | | -class ImportsHandler extends AbstractGoToHandler { |
| 19 | +class ImportsHandler |
| 20 | + extends SharedMessageHandler<TextDocumentPositionParams, List<Location>?> { |
17 | 21 | ImportsHandler(super.server); |
18 | 22 |
|
19 | 23 | @override |
20 | 24 | Method get handlesMessage => CustomMethods.imports; |
21 | 25 |
|
| 26 | + @override |
| 27 | + LspJsonHandler<TextDocumentPositionParams> get jsonHandler => |
| 28 | + TextDocumentPositionParams.jsonHandler; |
| 29 | + |
22 | 30 | @override |
23 | 31 | bool get requiresTrustedCaller => false; |
24 | 32 |
|
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 | 33 | @override |
32 | | - Either2<Location?, List<Location>> findRelatedLocations( |
33 | | - Element element, |
| 34 | + Future<ErrorOr<List<Location>?>> handle( |
| 35 | + TextDocumentPositionParams params, |
| 36 | + MessageInfo message, |
| 37 | + CancellationToken token, |
| 38 | + ) async { |
| 39 | + if (!isDartDocument(params.textDocument)) { |
| 40 | + return success(null); |
| 41 | + } |
| 42 | + |
| 43 | + var pos = params.position; |
| 44 | + var path = pathOfDoc(params.textDocument); |
| 45 | + var library = await path.mapResult(requireResolvedLibrary); |
| 46 | + var unit = (path, library).mapResultsSync<ResolvedUnitResult>(( |
| 47 | + path, |
| 48 | + library, |
| 49 | + ) { |
| 50 | + var unit = library.unitWithPath(path); |
| 51 | + return unit != null |
| 52 | + ? success(unit) |
| 53 | + : error( |
| 54 | + ErrorCodes.InternalError, |
| 55 | + 'The library containing a path did not contain the path.', |
| 56 | + ); |
| 57 | + }); |
| 58 | + var offset = unit.mapResultSync( |
| 59 | + (unit) => toOffset(unit.unit.lineInfo, pos), |
| 60 | + ); |
| 61 | + |
| 62 | + return (library, unit, offset).mapResults((library, unit, offset) async { |
| 63 | + var node = NodeLocator(offset).searchWithin(unit.unit); |
| 64 | + if (node == null) { |
| 65 | + return success(null); |
| 66 | + } |
| 67 | + |
| 68 | + var element = ElementLocator.locate2(node); |
| 69 | + if (element == null) { |
| 70 | + return success(null); |
| 71 | + } |
| 72 | + |
| 73 | + String? prefix; |
| 74 | + if (node is NamedType) { |
| 75 | + prefix = node.importPrefix?.name.lexeme; |
| 76 | + } else if (node.thisOrAncestorOfType<PrefixedIdentifier>() |
| 77 | + case PrefixedIdentifier identifier) { |
| 78 | + prefix = identifier.prefix.name; |
| 79 | + } else if (node is SimpleIdentifier) { |
| 80 | + if (node.parent case MethodInvocation( |
| 81 | + target: SimpleIdentifier target?, |
| 82 | + )) { |
| 83 | + prefix = target.toString(); |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + var enclosingElement = element.enclosingElement2; |
| 88 | + if (enclosingElement is ExtensionElement2) { |
| 89 | + element = enclosingElement; |
| 90 | + } |
| 91 | + |
| 92 | + var locations = _getImportLocations(element, library, unit, prefix); |
| 93 | + |
| 94 | + return success(nullIfEmpty(locations)); |
| 95 | + }); |
| 96 | + } |
| 97 | + |
| 98 | + /// Returns [Location]s for imports that import the given [element] into |
| 99 | + /// [unit]. |
| 100 | + List<Location> _getImportLocations( |
| 101 | + Element2 element, |
34 | 102 | ResolvedLibraryResult libraryResult, |
35 | 103 | ResolvedUnitResult unit, |
36 | 104 | String? prefix, |
37 | 105 | ) { |
38 | | - var elementName = element.name; |
| 106 | + var elementName = element.name3; |
39 | 107 | if (elementName == null) { |
40 | | - return Either2.t1(null); |
| 108 | + return []; |
41 | 109 | } |
42 | 110 |
|
43 | 111 | var imports = _getImports(libraryResult); |
| 112 | + var results = <Location>[]; |
44 | 113 |
|
45 | | - var directives = <ImportDirective>[]; |
46 | | - for (var (:directive, :import) in imports) { |
47 | | - Element? namespaceElement; |
| 114 | + for (var (:unit, :directive) in imports) { |
| 115 | + var import = directive.libraryImport; |
| 116 | + if (import == null) continue; |
48 | 117 |
|
49 | | - if (prefix == null) { |
50 | | - namespaceElement = import.namespace.get(elementName); |
51 | | - } else { |
52 | | - namespaceElement = import.namespace.getPrefixed(prefix, elementName); |
53 | | - } |
| 118 | + var importedElement = |
| 119 | + prefix == null |
| 120 | + ? import.namespace.get2(elementName) |
| 121 | + : import.namespace.getPrefixed2(prefix, elementName); |
54 | 122 |
|
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); |
| 123 | + var isMatch = |
| 124 | + element is MultiplyDefinedElement2 |
| 125 | + ? element.conflictingElements2.contains(importedElement) |
| 126 | + : element == importedElement; |
| 127 | + |
| 128 | + if (isMatch) { |
| 129 | + var uri = uriConverter.toClientUri( |
| 130 | + unit.declaredFragment!.source.fullName, |
| 131 | + ); |
| 132 | + var lineInfo = unit.lineInfo; |
| 133 | + var range = toRange(lineInfo, directive.offset, directive.length); |
| 134 | + results.add(Location(uri: uri, range: range)); |
61 | 135 | } |
62 | 136 | } |
63 | | - return Either2.t2(directives.map(_importToLocation).nonNulls.toList()); |
| 137 | + |
| 138 | + return results; |
64 | 139 | } |
65 | 140 |
|
66 | 141 | 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 | 142 | 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 | 143 |
|
83 | | - Location? _importToLocation(ImportDirective directive) { |
84 | | - var sourcePath = directive.element?.declaration.source?.fullName; |
85 | | - if (sourcePath == null) { |
86 | | - return null; |
| 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)); |
87 | 149 | } |
88 | 150 |
|
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 | | - ); |
| 151 | + return imports; |
99 | 152 | } |
100 | 153 | } |
0 commit comments