diff --git a/pkg/analysis_server/lib/src/computer/computer_lazy_type_hierarchy.dart b/pkg/analysis_server/lib/src/computer/computer_lazy_type_hierarchy.dart index 3768471966a..814c4cd6d6d 100644 --- a/pkg/analysis_server/lib/src/computer/computer_lazy_type_hierarchy.dart +++ b/pkg/analysis_server/lib/src/computer/computer_lazy_type_hierarchy.dart @@ -6,15 +6,14 @@ library; import 'package:analysis_server/src/services/search/search_engine.dart'; +import 'package:analysis_server/src/utilities/element_location2.dart'; import 'package:analyzer/dart/analysis/results.dart'; -import 'package:analyzer/dart/analysis/session.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/source/source_range.dart'; import 'package:analyzer/src/dart/element/element.dart'; import 'package:analyzer/src/utilities/extensions/ast.dart'; -import 'package:collection/collection.dart'; /// A lazy computer for Type Hierarchies. /// @@ -36,9 +35,9 @@ class DartLazyTypeHierarchyComputer { DartLazyTypeHierarchyComputer(this._result); - /// Finds subtypes for [Element] at [location]. + /// Finds subtypes for the [Element2] at [location]. Future?> findSubtypes( - TypeHierarchyItemLocation location, + ElementLocation2 location, SearchEngine searchEngine, ) async { var targetElement = await _findTargetElement(location); @@ -49,7 +48,7 @@ class DartLazyTypeHierarchyComputer { return _getSubtypes(targetElement, searchEngine); } - /// Finds supertypes for the [Element] at [location]. + /// Finds supertypes for the [Element2] at [location]. /// /// If [anchor] is provided, it will be used to navigate to the element at /// [location] to preserve type arguments that have been provided along the @@ -58,7 +57,7 @@ class DartLazyTypeHierarchyComputer { /// Anchors are included in returned types (where necessary to preserve type /// arguments) that can be used when calling for the next level of types. Future?> findSupertypes( - TypeHierarchyItemLocation location, { + ElementLocation2 location, { TypeHierarchyAnchor? anchor, }) async { var targetElement = await _findTargetElement(location); @@ -105,11 +104,11 @@ class DartLazyTypeHierarchyComputer { return type is InterfaceType ? TypeHierarchyItem.forType(type) : null; } - /// Locate the [Element] referenced by [location]. + /// Locate the [Element2] referenced by [location]. Future _findTargetElement( - TypeHierarchyItemLocation location, + ElementLocation2 location, ) async { - var element = await location._locateIn(_result.session); + var element = await location.locateIn(_result.session); return element is InterfaceElement2 ? element : null; } @@ -119,7 +118,7 @@ class DartLazyTypeHierarchyComputer { SearchEngine searchEngine, ) async { /// Helper to convert a [SearchMatch] to a [TypeHierarchyRelatedItem]. - TypeHierarchyRelatedItem toHierarchyItem(SearchMatch match) { + TypeHierarchyRelatedItem? toHierarchyItem(SearchMatch match) { var element = match.element2 as InterfaceElement2; var type = element.thisType; switch (match.kind) { @@ -145,6 +144,7 @@ class DartLazyTypeHierarchyComputer { return matches .where((match) => seenElements.add(match.element2)) .map(toHierarchyItem) + .nonNulls .toList(); } @@ -162,12 +162,13 @@ class DartLazyTypeHierarchyComputer { var mixins = type.mixins; var superclassConstraints = type.superclassConstraints; - var supertypes = [ - if (supertype != null) TypeHierarchyRelatedItem.extends_(supertype), - ...superclassConstraints.map(TypeHierarchyRelatedItem.constrainedTo), - ...interfaces.map(TypeHierarchyRelatedItem.implements), - ...mixins.map(TypeHierarchyRelatedItem.mixesIn), - ]; + var supertypes = + [ + if (supertype != null) TypeHierarchyRelatedItem.extends_(supertype), + ...superclassConstraints.map(TypeHierarchyRelatedItem.constrainedTo), + ...interfaces.map(TypeHierarchyRelatedItem.implements), + ...mixins.map(TypeHierarchyRelatedItem.mixesIn), + ].nonNulls.toList(); if (anchor != null) { for (var (index, item) in supertypes.indexed) { @@ -219,7 +220,7 @@ class DartLazyTypeHierarchyComputer { class TypeHierarchyAnchor { /// The location of the anchor element. - final TypeHierarchyItemLocation location; + final ElementLocation2 location; /// The supertype path from [location] to the target element. final List path; @@ -238,7 +239,7 @@ class TypeHierarchyItem { /// `findSubtypes`/`findSupertypes` so that if code has been modified since /// the `findTarget` call the element can still be located (provided the /// names/identifiers have not changed). - final TypeHierarchyItemLocation location; + final ElementLocation2 location; /// The type being displayed. final InterfaceType _type; @@ -261,15 +262,21 @@ class TypeHierarchyItem { required this.codeRange, }) : _type = type; - TypeHierarchyItem.forType(InterfaceType type) - : this( - type: type, - displayName: _displayNameForType(type), - location: TypeHierarchyItemLocation.forElement(type.element3), - nameRange: _nameRangeForElement(type.element3), - codeRange: _codeRangeForElement(type.element3), - file: type.element3.firstFragment.libraryFragment.source.fullName, - ); + TypeHierarchyItem._forType({ + required InterfaceType type, + required this.location, + }) : _type = type, + displayName = _displayNameForType(type), + nameRange = _nameRangeForElement(type.element3), + codeRange = _codeRangeForElement(type.element3), + file = type.element3.firstFragment.libraryFragment.source.fullName; + + static TypeHierarchyItem? forType(InterfaceType type) { + var location = ElementLocation2.forElement(type.element3); + if (location == null) return null; + + return TypeHierarchyItem._forType(type: type, location: location); + } /// Returns the [SourceRange] of the code for [element]. static SourceRange _codeRangeForElement(Element2 element) { @@ -296,58 +303,6 @@ class TypeHierarchyItem { } } -/// Represents the location of an item that can appear in Type Hierarchy that -/// can be encoded to/from a [String] for round-tripping to the client. -class TypeHierarchyItemLocation { - /// The URI of the Library that contains this type. - final String _libraryUri; - - /// The [Element2.name3] for this type. - final String _name; - - factory TypeHierarchyItemLocation.decode(String encoded) { - var parts = encoded.split(';'); - if (parts.length != 2) { - throw ArgumentError( - "Encoded string should be in format 'libraryUri;name' encoded", - ); - } - return TypeHierarchyItemLocation._(libraryUri: parts[0], name: parts[1]); - } - - factory TypeHierarchyItemLocation.forElement(InterfaceElement2 element) { - var name = element.name3; - - if (name == null) { - throw ArgumentError( - 'Cannot create TypeHierarchyItemLocation for an element with no name: $element', - ); - } - - return TypeHierarchyItemLocation._( - libraryUri: element.library2.uri.toString(), - name: name, - ); - } - - TypeHierarchyItemLocation._({ - required String libraryUri, - required String name, - }) : _name = name, - _libraryUri = libraryUri; - - String get encoding => '$_libraryUri;$_name'; - - Future _locateIn(AnalysisSession session) async { - var result = await session.getLibraryByUri(_libraryUri); - return result is LibraryElementResult - ? result.element2.children2 - .whereType() - .firstWhereOrNull((element2) => element2.name3 == _name) - : null; - } -} - enum TypeHierarchyItemRelationship { unknown, implements, @@ -363,29 +318,51 @@ class TypeHierarchyRelatedItem extends TypeHierarchyItem { TypeHierarchyAnchor? _anchor; - TypeHierarchyRelatedItem.constrainedTo(InterfaceType type) - : this._forType( - type, - relationship: TypeHierarchyItemRelationship.constrainedTo, - ); - TypeHierarchyRelatedItem.extends_(InterfaceType type) - : this._forType(type, relationship: TypeHierarchyItemRelationship.extends_); + TypeHierarchyRelatedItem({ + required super.type, + required this.relationship, + required super.displayName, + required super.location, + required super.file, + required super.nameRange, + required super.codeRange, + }); - TypeHierarchyRelatedItem.implements(InterfaceType type) - : this._forType( - type, - relationship: TypeHierarchyItemRelationship.implements, - ); - - TypeHierarchyRelatedItem.mixesIn(InterfaceType type) - : this._forType(type, relationship: TypeHierarchyItemRelationship.mixesIn); - - TypeHierarchyRelatedItem.unknown(InterfaceType type) - : this._forType(type, relationship: TypeHierarchyItemRelationship.unknown); - - TypeHierarchyRelatedItem._forType(super.type, {required this.relationship}) - : super.forType(); + TypeHierarchyRelatedItem.forType({ + required super.type, + required this.relationship, + required super.location, + }) : super._forType(); /// An optional anchor element used to preserve type args. TypeHierarchyAnchor? get anchor => _anchor; + + static TypeHierarchyRelatedItem? constrainedTo(InterfaceType type) => + _forType(type, relationship: TypeHierarchyItemRelationship.constrainedTo); + + static TypeHierarchyRelatedItem? extends_(InterfaceType type) => + _forType(type, relationship: TypeHierarchyItemRelationship.extends_); + + static TypeHierarchyRelatedItem? implements(InterfaceType type) => + _forType(type, relationship: TypeHierarchyItemRelationship.implements); + + static TypeHierarchyRelatedItem? mixesIn(InterfaceType type) => + _forType(type, relationship: TypeHierarchyItemRelationship.mixesIn); + + static TypeHierarchyRelatedItem? unknown(InterfaceType type) => + _forType(type, relationship: TypeHierarchyItemRelationship.unknown); + + static TypeHierarchyRelatedItem? _forType( + InterfaceType type, { + required TypeHierarchyItemRelationship relationship, + }) { + var location = ElementLocation2.forElement(type.element3); + if (location == null) return null; + + return TypeHierarchyRelatedItem.forType( + type: type, + relationship: relationship, + location: location, + ); + } } diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart index 32373f61626..c7a8b004d51 100644 --- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart +++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart @@ -23,13 +23,13 @@ import 'package:analysis_server/src/services/completion/yaml/pubspec_generator.d import 'package:analysis_server/src/services/completion/yaml/yaml_completion_generator.dart'; import 'package:analysis_server/src/services/snippets/dart_snippet_request.dart'; import 'package:analysis_server/src/services/snippets/snippet_manager.dart'; +import 'package:analysis_server/src/utilities/element_location2.dart'; import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/analysis/session.dart'; import 'package:analyzer/dart/ast/ast.dart' as ast; import 'package:analyzer/source/line_info.dart'; import 'package:analyzer/src/util/file_paths.dart' as file_paths; import 'package:analyzer/src/util/performance/operation_performance.dart'; -import 'package:analyzer/src/utilities/extensions/element.dart'; import 'package:analyzer/src/utilities/fuzzy_matcher.dart'; import 'package:analyzer_plugin/protocol/protocol_common.dart'; import 'package:analyzer_plugin/src/utilities/completion/completion_target.dart'; @@ -478,27 +478,26 @@ class CompletionHandler CompletionItemResolutionInfo? resolutionInfo; if (item is ElementBasedSuggestion && item is ImportableSuggestion) { - var elementLocation = - (item as ElementBasedSuggestion).element.asElement!.location; - var importUri = item.importData?.libraryUri; + var element = (item as ElementBasedSuggestion).element; + var importUri = item.importData?.libraryUri; if (importUri != null) { resolutionInfo = DartCompletionResolutionInfo( file: unit.path, importUris: [importUri.toString()], - ref: elementLocation?.encoding, + ref: ElementLocation2.forElement(element)?.encoding, ); } } else if (item is OverrideSuggestion) { var overrideData = item.data; if (overrideData != null && overrideData.imports.isNotEmpty) { - var elementLocation = - (item as ElementBasedSuggestion).element.location; + var element = (item as ElementBasedSuggestion).element; + var importUris = overrideData.imports; resolutionInfo = DartCompletionResolutionInfo( file: unit.path, importUris: importUris.map((uri) => uri.toString()).toList(), - ref: elementLocation?.encoding, + ref: ElementLocation2.forElement(element)?.encoding, ); } } diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart index f12e99fc55c..73065c5e7b4 100644 --- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart +++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart @@ -9,11 +9,9 @@ import 'package:analysis_server/src/lsp/constants.dart'; 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/utilities/element_location2.dart'; import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/analysis/session.dart'; -import 'package:analyzer/src/dart/element/element.dart'; -import 'package:analyzer/src/utilities/extensions/analysis_session.dart'; -import 'package:analyzer/src/utilities/extensions/element.dart'; import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; class CompletionResolveHandler @@ -63,11 +61,7 @@ class CompletionResolveHandler ) async { var file = data.file; var importUris = data.importUris.map(Uri.parse).toList(); - var elementLocationReference = data.ref; - var elementLocation = - elementLocationReference != null - ? ElementLocationImpl.con2(elementLocationReference) - : null; + var elementReference = data.ref; const timeout = Duration(milliseconds: 1000); var timer = Stopwatch()..start(); @@ -130,8 +124,10 @@ class CompletionResolveHandler // Look up documentation if we can get an element for this item. Either2? documentation; var element = - elementLocation != null - ? (await session.locateElement(elementLocation)).asElement2 + elementReference != null + ? await ElementLocation2.decode( + elementReference, + ).locateIn(session) : null; if (element != null) { var formats = clientCapabilities.completionDocumentationFormats; diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_type_hierarchy.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_type_hierarchy.dart index 88459347fc3..f7cf56dbd50 100644 --- a/pkg/analysis_server/lib/src/lsp/handlers/handler_type_hierarchy.dart +++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_type_hierarchy.dart @@ -12,6 +12,7 @@ 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/utilities/element_location2.dart'; import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/analysis/session.dart'; import 'package:analyzer/source/line_info.dart'; @@ -140,7 +141,7 @@ class TypeHierarchySubtypesHandler ); } - var location = type_hierarchy.TypeHierarchyItemLocation.decode(data.ref); + var location = ElementLocation2.decode(data.ref); var calls = await computer.findSubtypes(location, server.searchEngine); var results = calls != null ? _convertItems(unit, calls) : null; return success(results); @@ -188,7 +189,7 @@ class TypeHierarchySupertypesHandler ); } - var location = type_hierarchy.TypeHierarchyItemLocation.decode(data.ref); + var location = ElementLocation2.decode(data.ref); var anchor = _toServerAnchor(data); var calls = await computer.findSupertypes(location, anchor: anchor); var results = calls != null ? _convertItems(unit, calls) : null; @@ -204,7 +205,7 @@ class TypeHierarchySupertypesHandler var anchor = data.anchor; return anchor != null ? type_hierarchy.TypeHierarchyAnchor( - location: type_hierarchy.TypeHierarchyItemLocation.decode(anchor.ref), + location: ElementLocation2.decode(anchor.ref), path: anchor.path, ) : null; diff --git a/pkg/analysis_server/lib/src/utilities/element_location2.dart b/pkg/analysis_server/lib/src/utilities/element_location2.dart new file mode 100644 index 00000000000..69bdbb670f2 --- /dev/null +++ b/pkg/analysis_server/lib/src/utilities/element_location2.dart @@ -0,0 +1,106 @@ +// Copyright (c) 2024, 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:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/dart/analysis/session.dart'; +import 'package:analyzer/dart/element/element2.dart'; +import 'package:collection/collection.dart'; + +/// Represents the location of an [Element2] that exists in a chain of elements +/// from a [LibraryElement2]. +/// +/// Elements with [Fragment]s or Elements with `null` names anywhere in the +/// chain are not representable. +class ElementLocation2 { + /// The library that this element belongs to. + final String _libraryUri; + + /// The [Element2.lookupName] for this element, or the containing element if + /// this location is a [_MemberElementLocation2]. + final String _topLevelName; + + factory ElementLocation2.decode(String encoded) { + var components = encoded.split(';'); + return switch (components) { + [String library, String topName] => ElementLocation2._(library, topName), + [String library, String topName, String memberName] => + _MemberElementLocation2._(library, topName, memberName), + _ => + throw ArgumentError.value( + encoded, + 'encoded', + "Encoded string should be in the format 'libraryUri;topLevelName[;memberName]'", + ), + }; + } + + ElementLocation2._(this._libraryUri, this._topLevelName); + + String get encoding => '$_libraryUri;$_topLevelName'; + + /// Locates the [Element2] represented by this [ElementLocation2] in + /// [session]. + /// + /// Returns `null` if the [Element2] cannot be located. + Future locateIn(AnalysisSession session) async { + var result = await session.getLibraryByUri(_libraryUri); + if (result is! LibraryElementResult) return null; + + return result.element2.children2.firstWhereOrNull( + (child) => child.lookupName == _topLevelName, + ); + } + + /// Gets an [ElementLocation2] for this element. + /// + /// Returns `null` if this element is neither a top level element or a + /// member of a top level element, or if either do not have a `lookupName`. + static ElementLocation2? forElement(Element2 element) { + var library = element.library2; + if (library == null) return null; + var libraryUri = library.uri.toString(); + + if (element.enclosingElement2 == library) { + var topName = element.lookupName; + + return topName != null ? ElementLocation2._(libraryUri, topName) : null; + } else if (element.enclosingElement2?.enclosingElement2 == library) { + var memberName = element.lookupName; + var topName = element.enclosingElement2?.lookupName; + + return topName != null && memberName != null + ? _MemberElementLocation2._(libraryUri, topName, memberName) + : null; + } else { + return null; + } + } +} + +class _MemberElementLocation2 extends ElementLocation2 { + /// The [Element2.lookupName] for this member within [_topLevelName]. + final String _memberName; + + _MemberElementLocation2._( + super.libraryUri, + super.topLevelName, + this._memberName, + ) : super._(); + + @override + String get encoding => '${super.encoding};$_memberName'; + + /// Locates the [Element2] represented by this [_MemberElementLocation2] in + /// [session]. + /// + /// Returns `null` if the [Element2] cannot be located. + @override + Future locateIn(AnalysisSession session) async { + var topLevel = await super.locateIn(session); + + return topLevel?.children2.firstWhereOrNull( + (child) => child.lookupName == _memberName, + ); + } +} diff --git a/pkg/analysis_server/tool/lsp_spec/generate_all.dart b/pkg/analysis_server/tool/lsp_spec/generate_all.dart index 166883ccc2d..21793047a01 100644 --- a/pkg/analysis_server/tool/lsp_spec/generate_all.dart +++ b/pkg/analysis_server/tool/lsp_spec/generate_all.dart @@ -369,7 +369,7 @@ List getCustomClasses() { type: 'string', canBeUndefined: true, comment: - 'The ElementLocation of the item being completed.\n\n' + 'The encoded ElementLocation2 of the item being completed.\n\n' 'This is used to provide documentation in the resolved response.', ), ], baseType: 'CompletionItemResolutionInfo'), diff --git a/third_party/pkg/language_server_protocol/lib/protocol_custom_generated.dart b/third_party/pkg/language_server_protocol/lib/protocol_custom_generated.dart index 27260469128..e21ef90168b 100644 --- a/third_party/pkg/language_server_protocol/lib/protocol_custom_generated.dart +++ b/third_party/pkg/language_server_protocol/lib/protocol_custom_generated.dart @@ -1106,7 +1106,7 @@ class DartCompletionResolutionInfo /// The URIs to be imported if this completion is selected. final List importUris; - /// The ElementLocation of the item being completed. + /// The encoded ElementLocation2 of the item being completed. /// /// This is used to provide documentation in the resolved response. final String? ref;