[analysis_server] Extract TypeHierarchyItemLocation as ElementLocation2 and use for completion resolution

This moves completion resolution off `ElementLocation` onto the class recently created as `TypeHierarchyItemLocation`, which is now renamed to `ElementLocation2` and extracted to its own file.

Change-Id: I1f0b831ded7b08d6c09f97fcfd66f38f1dd4750e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/401021
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Danny Tuppeny
2024-12-19 14:14:53 -08:00
committed by Commit Queue
parent 466fa5593d
commit 76623a6541
7 changed files with 202 additions and 123 deletions
@@ -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<List<TypeHierarchyRelatedItem>?> 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<List<TypeHierarchyRelatedItem>?> 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<InterfaceElement2?> _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<int> 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<InterfaceElement2?> _locateIn(AnalysisSession session) async {
var result = await session.getLibraryByUri(_libraryUri);
return result is LibraryElementResult
? result.element2.children2
.whereType<InterfaceElement2>()
.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,
);
}
}
@@ -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,
);
}
}
@@ -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<MarkupContent, String>? 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;
@@ -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;
@@ -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<Element2?> 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<Element2?> locateIn(AnalysisSession session) async {
var topLevel = await super.locateIn(session);
return topLevel?.children2.firstWhereOrNull(
(child) => child.lookupName == _memberName,
);
}
}
@@ -369,7 +369,7 @@ List<LspEntity> 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'),
@@ -1106,7 +1106,7 @@ class DartCompletionResolutionInfo
/// The URIs to be imported if this completion is selected.
final List<String> 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;