| // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:analysis_server/lsp_protocol/protocol.dart' hide Element; |
| import 'package:analysis_server/src/lsp/error_or.dart'; |
| import 'package:analysis_server/src/lsp/handlers/handlers.dart'; |
| import 'package:analysis_server/src/lsp/mapping.dart'; |
| import 'package:analysis_server/src/lsp/registration/feature_registration.dart'; |
| import 'package:analysis_server/src/search/element_references.dart'; |
| import 'package:analysis_server/src/services/search/search_engine.dart' |
| show SearchMatch; |
| import 'package:analysis_server/src/utilities/extensions/ast.dart'; |
| import 'package:analyzer/dart/analysis/results.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/src/util/performance/operation_performance.dart'; |
| |
| typedef StaticOptions = Either2<bool, ReferenceOptions>; |
| |
| class ReferencesHandler |
| extends LspMessageHandler<ReferenceParams, List<Location>?> { |
| ReferencesHandler(super.server); |
| |
| @override |
| Method get handlesMessage => Method.textDocument_references; |
| |
| @override |
| LspJsonHandler<ReferenceParams> get jsonHandler => |
| ReferenceParams.jsonHandler; |
| |
| @override |
| Future<ErrorOr<List<Location>?>> handle( |
| ReferenceParams params, |
| MessageInfo message, |
| CancellationToken token, |
| ) async { |
| if (!isDartDocument(params.textDocument)) { |
| return success(const []); |
| } |
| |
| var pos = params.position; |
| var path = pathOfDoc(params.textDocument); |
| var unit = await path.mapResult(requireResolvedUnit); |
| var offset = unit.mapResultSync((unit) => toOffset(unit.lineInfo, pos)); |
| return await message.performance.runAsync( |
| '_getReferences', |
| (performance) => (unit, offset).mapResults( |
| (unit, offset) => _getReferences(unit, offset, params, performance), |
| ), |
| ); |
| } |
| |
| List<Location> _getDeclarations(Element element) { |
| return element.nonSynthetic.fragments |
| .map((fragment) => fragmentToLocation(uriConverter, fragment)) |
| .nonNulls |
| .toList(); |
| } |
| |
| Future<ErrorOr<List<Location>?>> _getReferences( |
| ResolvedUnitResult result, |
| int offset, |
| ReferenceParams params, |
| OperationPerformanceImpl performance, |
| ) async { |
| var node = result.unit.nodeCovering(offset: offset); |
| node = _getReferenceTargetNode(node); |
| |
| var element = switch (node?.getElement()) { |
| FieldFormalParameterElement(:var field?) => field, |
| PropertyAccessorElement(:var variable?) => variable, |
| var element => element, |
| }; |
| |
| if (element == null) { |
| return success(null); |
| } |
| |
| var computer = ElementReferencesComputer(server.searchEngine); |
| var session = element.session ?? result.session; |
| |
| var results = await performance.runAsync( |
| 'computer.compute', |
| (childPerformance) => |
| computer.compute(element, false, performance: childPerformance), |
| ); |
| |
| Location? toLocation(SearchMatch result) { |
| var file = session.getFile(result.file); |
| if (file is! FileResult) { |
| return null; |
| } |
| return Location( |
| uri: uriConverter.toClientUri(result.file), |
| range: toRange( |
| file.lineInfo, |
| result.sourceRange.offset, |
| result.sourceRange.length, |
| ), |
| ); |
| } |
| |
| var referenceResults = performance.run( |
| 'convert', |
| (_) => convert(results, toLocation).nonNulls.toList(), |
| ); |
| |
| if (params.context.includeDeclaration == true) { |
| // Also include the definition for the resolved element. |
| referenceResults.addAll( |
| performance.run('_getDeclarations', (_) => _getDeclarations(element)), |
| ); |
| } |
| |
| return success(referenceResults); |
| } |
| |
| /// Gets the nearest node that should be used for finding references. |
| /// |
| /// This is usually the same node but allows some adjustments such as |
| /// considering the offset between a type name and type arguments as part |
| /// of the type. |
| AstNode? _getReferenceTargetNode(AstNode? node) { |
| // Consider the angle brackets for type arguments part of the leading type, |
| // otherwise we don't navigate in the common situation of having the type |
| // name selected, where VS Code provides the end of the selection as the |
| // position to search. |
| // |
| // In `A^<String>` node will be `TypeParameterList` and we will not find any |
| // references. |
| if (node is TypeParameterList) { |
| node = node.parent; |
| } |
| |
| return node; |
| } |
| } |
| |
| class ReferencesRegistrations extends FeatureRegistration |
| with SingleDynamicRegistration, StaticRegistration<StaticOptions> { |
| ReferencesRegistrations(super.info); |
| |
| @override |
| ToJsonable? get options => |
| TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes); |
| |
| @override |
| Method get registrationMethod => Method.textDocument_references; |
| |
| @override |
| StaticOptions get staticOptions => Either2.t1(true); |
| |
| @override |
| bool get supportsDynamic => clientDynamic.references; |
| } |