mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 08:24:39 +00:00
[analysis_server] Include type arguments in Type Hierarchy
Fixes https://github.com/Dart-Code/Dart-Code/issues/4217. Change-Id: I8b8dec4ad25a9eb4a4f80dd036e8a9b61bb012d2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/264981 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
parent
96294014e3
commit
27ba8fce6c
|
@ -1858,6 +1858,147 @@ class SnippetTextEdit implements TextEdit, ToJsonable {
|
|||
String toString() => jsonEncoder.convert(toJson());
|
||||
}
|
||||
|
||||
class TypeHierarchyAnchor implements ToJsonable {
|
||||
static const jsonHandler = LspJsonHandler(
|
||||
TypeHierarchyAnchor.canParse,
|
||||
TypeHierarchyAnchor.fromJson,
|
||||
);
|
||||
|
||||
TypeHierarchyAnchor({
|
||||
required this.path,
|
||||
required this.ref,
|
||||
});
|
||||
static TypeHierarchyAnchor fromJson(Map<String, Object?> json) {
|
||||
final pathJson = json['path'];
|
||||
final path =
|
||||
(pathJson as List<Object?>).map((item) => item as int).toList();
|
||||
final refJson = json['ref'];
|
||||
final ref = refJson as String;
|
||||
return TypeHierarchyAnchor(
|
||||
path: path,
|
||||
ref: ref,
|
||||
);
|
||||
}
|
||||
|
||||
/// Indices used to navigate from this anchor to the element.
|
||||
final List<int> path;
|
||||
|
||||
/// The ElementLocation for this anchor element.
|
||||
final String ref;
|
||||
|
||||
@override
|
||||
Map<String, Object?> toJson() {
|
||||
var result = <String, Object?>{};
|
||||
result['path'] = path;
|
||||
result['ref'] = ref;
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool canParse(Object? obj, LspJsonReporter reporter) {
|
||||
if (obj is Map<String, Object?>) {
|
||||
if (!_canParseListInt(obj, reporter, 'path',
|
||||
allowsUndefined: false, allowsNull: false)) {
|
||||
return false;
|
||||
}
|
||||
return _canParseString(obj, reporter, 'ref',
|
||||
allowsUndefined: false, allowsNull: false);
|
||||
} else {
|
||||
reporter.reportError('must be of type TypeHierarchyAnchor');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TypeHierarchyAnchor &&
|
||||
other.runtimeType == TypeHierarchyAnchor &&
|
||||
listEqual(path, other.path, (int a, int b) => a == b) &&
|
||||
ref == other.ref;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
lspHashCode(path),
|
||||
ref,
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() => jsonEncoder.convert(toJson());
|
||||
}
|
||||
|
||||
class TypeHierarchyItemInfo implements ToJsonable {
|
||||
static const jsonHandler = LspJsonHandler(
|
||||
TypeHierarchyItemInfo.canParse,
|
||||
TypeHierarchyItemInfo.fromJson,
|
||||
);
|
||||
|
||||
TypeHierarchyItemInfo({
|
||||
this.anchor,
|
||||
required this.ref,
|
||||
});
|
||||
static TypeHierarchyItemInfo fromJson(Map<String, Object?> json) {
|
||||
final anchorJson = json['anchor'];
|
||||
final anchor = anchorJson != null
|
||||
? TypeHierarchyAnchor.fromJson(anchorJson as Map<String, Object?>)
|
||||
: null;
|
||||
final refJson = json['ref'];
|
||||
final ref = refJson as String;
|
||||
return TypeHierarchyItemInfo(
|
||||
anchor: anchor,
|
||||
ref: ref,
|
||||
);
|
||||
}
|
||||
|
||||
/// An anchor element that can be used to navigate to this element preserving
|
||||
/// type arguments.
|
||||
final TypeHierarchyAnchor? anchor;
|
||||
|
||||
/// The ElementLocation for this element, used to re-locate the element when
|
||||
/// subtypes/supertypes are fetched later.
|
||||
final String ref;
|
||||
|
||||
@override
|
||||
Map<String, Object?> toJson() {
|
||||
var result = <String, Object?>{};
|
||||
if (anchor != null) {
|
||||
result['anchor'] = anchor?.toJson();
|
||||
}
|
||||
result['ref'] = ref;
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool canParse(Object? obj, LspJsonReporter reporter) {
|
||||
if (obj is Map<String, Object?>) {
|
||||
if (!_canParseTypeHierarchyAnchor(obj, reporter, 'anchor',
|
||||
allowsUndefined: true, allowsNull: false)) {
|
||||
return false;
|
||||
}
|
||||
return _canParseString(obj, reporter, 'ref',
|
||||
allowsUndefined: false, allowsNull: false);
|
||||
} else {
|
||||
reporter.reportError('must be of type TypeHierarchyItemInfo');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is TypeHierarchyItemInfo &&
|
||||
other.runtimeType == TypeHierarchyItemInfo &&
|
||||
anchor == other.anchor &&
|
||||
ref == other.ref;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
anchor,
|
||||
ref,
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() => jsonEncoder.convert(toJson());
|
||||
}
|
||||
|
||||
class ValidateRefactorResult implements ToJsonable {
|
||||
static const jsonHandler = LspJsonHandler(
|
||||
ValidateRefactorResult.canParse,
|
||||
|
@ -2184,6 +2325,32 @@ bool _canParseListFlutterOutlineAttribute(
|
|||
return true;
|
||||
}
|
||||
|
||||
bool _canParseListInt(
|
||||
Map<String, Object?> map, LspJsonReporter reporter, String fieldName,
|
||||
{required bool allowsUndefined, required bool allowsNull}) {
|
||||
reporter.push(fieldName);
|
||||
try {
|
||||
if (!allowsUndefined && !map.containsKey(fieldName)) {
|
||||
reporter.reportError('must not be undefined');
|
||||
return false;
|
||||
}
|
||||
final value = map[fieldName];
|
||||
final nullCheck = allowsNull || allowsUndefined;
|
||||
if (!nullCheck && value == null) {
|
||||
reporter.reportError('must not be null');
|
||||
return false;
|
||||
}
|
||||
if ((!nullCheck || value != null) &&
|
||||
(value is! List<Object?> || value.any((item) => item is! int))) {
|
||||
reporter.reportError('must be of type List<int>');
|
||||
return false;
|
||||
}
|
||||
} finally {
|
||||
reporter.pop();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _canParseListOutline(
|
||||
Map<String, Object?> map, LspJsonReporter reporter, String fieldName,
|
||||
{required bool allowsUndefined, required bool allowsNull}) {
|
||||
|
@ -2395,6 +2562,32 @@ bool _canParseString(
|
|||
return true;
|
||||
}
|
||||
|
||||
bool _canParseTypeHierarchyAnchor(
|
||||
Map<String, Object?> map, LspJsonReporter reporter, String fieldName,
|
||||
{required bool allowsUndefined, required bool allowsNull}) {
|
||||
reporter.push(fieldName);
|
||||
try {
|
||||
if (!allowsUndefined && !map.containsKey(fieldName)) {
|
||||
reporter.reportError('must not be undefined');
|
||||
return false;
|
||||
}
|
||||
final value = map[fieldName];
|
||||
final nullCheck = allowsNull || allowsUndefined;
|
||||
if (!nullCheck && value == null) {
|
||||
reporter.reportError('must not be null');
|
||||
return false;
|
||||
}
|
||||
if ((!nullCheck || value != null) &&
|
||||
!TypeHierarchyAnchor.canParse(value, reporter)) {
|
||||
reporter.reportError('must be of type TypeHierarchyAnchor');
|
||||
return false;
|
||||
}
|
||||
} finally {
|
||||
reporter.pop();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _canParseUri(
|
||||
Map<String, Object?> map, LspJsonReporter reporter, String fieldName,
|
||||
{required bool allowsUndefined, required bool allowsNull}) {
|
||||
|
|
|
@ -31448,7 +31448,9 @@ class TypeHierarchyItem implements ToJsonable {
|
|||
});
|
||||
static TypeHierarchyItem fromJson(Map<String, Object?> json) {
|
||||
final dataJson = json['data'];
|
||||
final data = dataJson;
|
||||
final data = dataJson != null
|
||||
? TypeHierarchyItemInfo.fromJson(dataJson as Map<String, Object?>)
|
||||
: null;
|
||||
final detailJson = json['detail'];
|
||||
final detail = detailJson as String?;
|
||||
final kindJson = json['kind'];
|
||||
|
@ -31482,7 +31484,7 @@ class TypeHierarchyItem implements ToJsonable {
|
|||
/// supertypes or subtypes requests. It could also be used to identify the
|
||||
/// type hierarchy in the server, helping improve the performance on resolving
|
||||
/// supertypes and subtypes.
|
||||
final LSPAny data;
|
||||
final TypeHierarchyItemInfo? data;
|
||||
|
||||
/// More detail for this item, e.g. the signature of a function.
|
||||
final String? detail;
|
||||
|
@ -31511,7 +31513,7 @@ class TypeHierarchyItem implements ToJsonable {
|
|||
Map<String, Object?> toJson() {
|
||||
var result = <String, Object?>{};
|
||||
if (data != null) {
|
||||
result['data'] = data;
|
||||
result['data'] = data?.toJson();
|
||||
}
|
||||
if (detail != null) {
|
||||
result['detail'] = detail;
|
||||
|
@ -31529,6 +31531,10 @@ class TypeHierarchyItem implements ToJsonable {
|
|||
|
||||
static bool canParse(Object? obj, LspJsonReporter reporter) {
|
||||
if (obj is Map<String, Object?>) {
|
||||
if (!_canParseTypeHierarchyItemInfo(obj, reporter, 'data',
|
||||
allowsUndefined: true, allowsNull: false)) {
|
||||
return false;
|
||||
}
|
||||
if (!_canParseString(obj, reporter, 'detail',
|
||||
allowsUndefined: true, allowsNull: false)) {
|
||||
return false;
|
||||
|
@ -41981,6 +41987,32 @@ bool _canParseTypeHierarchyItem(
|
|||
return true;
|
||||
}
|
||||
|
||||
bool _canParseTypeHierarchyItemInfo(
|
||||
Map<String, Object?> map, LspJsonReporter reporter, String fieldName,
|
||||
{required bool allowsUndefined, required bool allowsNull}) {
|
||||
reporter.push(fieldName);
|
||||
try {
|
||||
if (!allowsUndefined && !map.containsKey(fieldName)) {
|
||||
reporter.reportError('must not be undefined');
|
||||
return false;
|
||||
}
|
||||
final value = map[fieldName];
|
||||
final nullCheck = allowsNull || allowsUndefined;
|
||||
if (!nullCheck && value == null) {
|
||||
reporter.reportError('must not be null');
|
||||
return false;
|
||||
}
|
||||
if ((!nullCheck || value != null) &&
|
||||
!TypeHierarchyItemInfo.canParse(value, reporter)) {
|
||||
reporter.reportError('must be of type TypeHierarchyItemInfo');
|
||||
return false;
|
||||
}
|
||||
} finally {
|
||||
reporter.pop();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _canParseUniquenessLevel(
|
||||
Map<String, Object?> map, LspJsonReporter reporter, String fieldName,
|
||||
{required bool allowsUndefined, required bool allowsNull}) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'package:analyzer/dart/element/type.dart';
|
|||
import 'package:analyzer/source/source_range.dart';
|
||||
import 'package:analyzer/src/dart/ast/utilities.dart';
|
||||
import 'package:analyzer/src/dart/element/element.dart';
|
||||
import 'package:analyzer/src/utilities/extensions/analysis_session.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
/// A lazy computer for Type Hierarchies.
|
||||
|
@ -32,12 +33,12 @@ class DartLazyTypeHierarchyComputer {
|
|||
|
||||
DartLazyTypeHierarchyComputer(this._result);
|
||||
|
||||
/// Finds subtypes for [target].
|
||||
/// Finds subtypes for [Element] at [location].
|
||||
Future<List<TypeHierarchyRelatedItem>?> findSubtypes(
|
||||
TypeHierarchyItem target,
|
||||
ElementLocation location,
|
||||
SearchEngine searchEngine,
|
||||
) async {
|
||||
final targetElement = _findTargetElement(target);
|
||||
final targetElement = await _findTargetElement(location);
|
||||
if (targetElement is! InterfaceElement) {
|
||||
return null;
|
||||
}
|
||||
|
@ -45,16 +46,37 @@ class DartLazyTypeHierarchyComputer {
|
|||
return _getSubtypes(targetElement, searchEngine);
|
||||
}
|
||||
|
||||
/// Finds supertypes for [target].
|
||||
/// Finds supertypes for the [Element] 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
|
||||
/// way.
|
||||
///
|
||||
/// 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(
|
||||
TypeHierarchyItem target,
|
||||
) async {
|
||||
final targetElement = _findTargetElement(target);
|
||||
ElementLocation location, {
|
||||
TypeHierarchyAnchor? anchor,
|
||||
}) async {
|
||||
var targetElement = await _findTargetElement(location);
|
||||
if (targetElement == null) {
|
||||
return null;
|
||||
}
|
||||
var targetType = targetElement.thisType;
|
||||
|
||||
return _getSupertypes(targetElement);
|
||||
// If we were provided an anchor, use it to re-locate the target type so
|
||||
// that any type arguments supplied along the way will be preserved in the
|
||||
// new node.
|
||||
if (anchor != null) {
|
||||
targetType =
|
||||
await _locateTargetFromAnchor(anchor, targetType) ?? targetType;
|
||||
}
|
||||
|
||||
// If we had no existing anchor, create one that starts from this target as
|
||||
// the starting point for the new supertypes.
|
||||
anchor ??= TypeHierarchyAnchor(location: location, path: []);
|
||||
|
||||
return _getSupertypes(targetType, anchor: anchor);
|
||||
}
|
||||
|
||||
/// Finds a target for starting type hierarchy navigation at [offset].
|
||||
|
@ -74,19 +96,15 @@ class DartLazyTypeHierarchyComputer {
|
|||
element = declaration?.declaredElement;
|
||||
}
|
||||
|
||||
return element != null ? TypeHierarchyItem.forElement(element) : null;
|
||||
return element is InterfaceElement
|
||||
? TypeHierarchyItem.forType(element.thisType)
|
||||
: null;
|
||||
}
|
||||
|
||||
Element? _findTargetElement(TypeHierarchyItem target) {
|
||||
assert(target.file == _result.path);
|
||||
// Locate the element by name instead of offset since this call my occur
|
||||
// much later than the original find target request and it's possible the
|
||||
// user has made changes since.
|
||||
final targetElement = _result.unit.declarations
|
||||
.where(_isValidTargetDeclaration)
|
||||
.map((declaration) => declaration.declaredElement)
|
||||
.firstWhereOrNull((element) => element?.name == target.displayName);
|
||||
return targetElement;
|
||||
/// Locate the [Element] referenced by [location].
|
||||
Future<InterfaceElement?> _findTargetElement(ElementLocation location) async {
|
||||
final element = await _result.session.locateElement(location);
|
||||
return element is InterfaceElement ? element : null;
|
||||
}
|
||||
|
||||
/// Gets immediate sub types for the class/mixin [element].
|
||||
|
@ -129,20 +147,36 @@ class DartLazyTypeHierarchyComputer {
|
|||
/// Includes all elements that contribute implementation to the type
|
||||
/// such as supertypes and mixins, but not interfaces, constraints or
|
||||
/// extended types.
|
||||
List<TypeHierarchyRelatedItem> _getSupertypes(Element element) {
|
||||
final supertype = element is InterfaceElement ? element.supertype : null;
|
||||
final interfaces =
|
||||
element is InterfaceOrAugmentationElement ? element.interfaces : null;
|
||||
final mixins =
|
||||
element is InterfaceOrAugmentationElement ? element.mixins : null;
|
||||
final superclassConstraints =
|
||||
element is MixinElement ? element.superclassConstraints : null;
|
||||
return [
|
||||
List<TypeHierarchyRelatedItem> _getSupertypes(
|
||||
InterfaceType type, {
|
||||
TypeHierarchyAnchor? anchor,
|
||||
}) {
|
||||
final supertype = type.superclass;
|
||||
final interfaces = type.interfaces;
|
||||
final mixins = type.mixins;
|
||||
final superclassConstraints = type.superclassConstraints;
|
||||
|
||||
final supertypes = [
|
||||
if (supertype != null) TypeHierarchyRelatedItem.extends_(supertype),
|
||||
...?superclassConstraints?.map(TypeHierarchyRelatedItem.constrainedTo),
|
||||
...?interfaces?.map(TypeHierarchyRelatedItem.implements),
|
||||
...?mixins?.map(TypeHierarchyRelatedItem.mixesIn),
|
||||
...superclassConstraints.map(TypeHierarchyRelatedItem.constrainedTo),
|
||||
...interfaces.map(TypeHierarchyRelatedItem.implements),
|
||||
...mixins.map(TypeHierarchyRelatedItem.mixesIn),
|
||||
];
|
||||
|
||||
if (anchor != null) {
|
||||
supertypes.forEachIndexed((index, item) {
|
||||
// We only need to carry the anchor along if the supertype has type
|
||||
// arguments that we may be populating.
|
||||
if (item._type.typeArguments.isNotEmpty) {
|
||||
item._anchor = TypeHierarchyAnchor(
|
||||
location: anchor.location,
|
||||
path: [...anchor.path, index],
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return supertypes;
|
||||
}
|
||||
|
||||
/// Returns whether [declaration] is a valid target for type hierarchy
|
||||
|
@ -151,6 +185,37 @@ class DartLazyTypeHierarchyComputer {
|
|||
declaration is ClassDeclaration ||
|
||||
declaration is MixinDeclaration ||
|
||||
declaration is EnumDeclaration;
|
||||
|
||||
/// Navigate to [target] from [anchor], preserving type arguments supplied
|
||||
/// along the way.
|
||||
Future<InterfaceType?> _locateTargetFromAnchor(
|
||||
TypeHierarchyAnchor anchor, InterfaceType target) async {
|
||||
// Start from the anchor.
|
||||
final anchorElement = await _findTargetElement(anchor.location);
|
||||
final anchorPath = anchor.path;
|
||||
|
||||
// Follow the provided path.
|
||||
var type = anchorElement?.thisType;
|
||||
for (int i = 0; i < anchorPath.length && type != null; i++) {
|
||||
final index = anchorPath[i];
|
||||
final supertypes = _getSupertypes(type);
|
||||
type = supertypes.length >= index + 1 ? supertypes[index]._type : null;
|
||||
}
|
||||
|
||||
// Verify the element we arrived at matches the targetElement to guard
|
||||
// against code changes that made the path from the anchor invalid.
|
||||
return type != null && type.element == target.element ? type : null;
|
||||
}
|
||||
}
|
||||
|
||||
class TypeHierarchyAnchor {
|
||||
/// The location of the anchor element.
|
||||
final ElementLocation location;
|
||||
|
||||
/// The supertype path from [location] to the target element.
|
||||
final List<int> path;
|
||||
|
||||
TypeHierarchyAnchor({required this.location, required this.path});
|
||||
}
|
||||
|
||||
/// An item that can appear in a Type Hierarchy.
|
||||
|
@ -158,6 +223,17 @@ class TypeHierarchyItem {
|
|||
/// The user-visible name for this item in the Type Hierarchy.
|
||||
final String displayName;
|
||||
|
||||
/// The location of the element being displayed.
|
||||
///
|
||||
/// This is used to re-locate the element when calling
|
||||
/// `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 ElementLocation location;
|
||||
|
||||
/// The type being displayed.
|
||||
final InterfaceType _type;
|
||||
|
||||
/// The file that contains the declaration of this item.
|
||||
final String file;
|
||||
|
||||
|
@ -168,17 +244,23 @@ class TypeHierarchyItem {
|
|||
final SourceRange codeRange;
|
||||
|
||||
TypeHierarchyItem({
|
||||
required InterfaceType type,
|
||||
required this.displayName,
|
||||
required this.location,
|
||||
required this.file,
|
||||
required this.nameRange,
|
||||
required this.codeRange,
|
||||
});
|
||||
}) : _type = type;
|
||||
|
||||
TypeHierarchyItem.forElement(Element element)
|
||||
: displayName = element.displayName,
|
||||
nameRange = _nameRangeForElement(element),
|
||||
codeRange = _codeRangeForElement(element),
|
||||
file = element.source!.fullName;
|
||||
TypeHierarchyItem.forType(InterfaceType type)
|
||||
: this(
|
||||
type: type,
|
||||
displayName: _displayNameForType(type),
|
||||
location: type.element.location!,
|
||||
nameRange: _nameRangeForElement(type.element),
|
||||
codeRange: _codeRangeForElement(type.element),
|
||||
file: type.element.source.fullName,
|
||||
);
|
||||
|
||||
/// Returns the [SourceRange] of the code for [element].
|
||||
static SourceRange _codeRangeForElement(Element element) {
|
||||
|
@ -187,6 +269,11 @@ class TypeHierarchyItem {
|
|||
return SourceRange(elementImpl.codeOffset!, elementImpl.codeLength!);
|
||||
}
|
||||
|
||||
/// Returns a name to display in the hierarchy for [type].
|
||||
static String _displayNameForType(InterfaceType type) {
|
||||
return type.getDisplayString(withNullability: false);
|
||||
}
|
||||
|
||||
/// Returns the [SourceRange] of the name for [element].
|
||||
static SourceRange _nameRangeForElement(Element element) {
|
||||
element = element.nonSynthetic;
|
||||
|
@ -208,32 +295,35 @@ enum TypeHierarchyItemRelationship {
|
|||
mixesIn,
|
||||
}
|
||||
|
||||
/// A supertype of subtype of a [TypeHierarchyItem].
|
||||
/// A supertype or subtype of a [TypeHierarchyItem].
|
||||
class TypeHierarchyRelatedItem extends TypeHierarchyItem {
|
||||
/// The relationship this item has with the target item.
|
||||
final TypeHierarchyItemRelationship relationship;
|
||||
|
||||
TypeHierarchyRelatedItem.constrainedTo(InterfaceType type)
|
||||
: this._forElement(type.element,
|
||||
relationship: TypeHierarchyItemRelationship.constrainedTo);
|
||||
TypeHierarchyAnchor? _anchor;
|
||||
|
||||
TypeHierarchyRelatedItem.constrainedTo(InterfaceType type)
|
||||
: this._forType(type,
|
||||
relationship: TypeHierarchyItemRelationship.constrainedTo);
|
||||
TypeHierarchyRelatedItem.extends_(InterfaceType type)
|
||||
: this._forElement(type.element,
|
||||
: this._forType(type,
|
||||
relationship: TypeHierarchyItemRelationship.extends_);
|
||||
|
||||
TypeHierarchyRelatedItem.implements(InterfaceType type)
|
||||
: this._forElement(type.element,
|
||||
: this._forType(type,
|
||||
relationship: TypeHierarchyItemRelationship.implements);
|
||||
|
||||
TypeHierarchyRelatedItem.mixesIn(InterfaceType type)
|
||||
: this._forElement(type.element,
|
||||
: this._forType(type,
|
||||
relationship: TypeHierarchyItemRelationship.mixesIn);
|
||||
|
||||
TypeHierarchyRelatedItem.unknown(InterfaceType type)
|
||||
: this._forElement(type.element,
|
||||
: this._forType(type,
|
||||
relationship: TypeHierarchyItemRelationship.unknown);
|
||||
|
||||
TypeHierarchyRelatedItem._forElement(super.element,
|
||||
{required this.relationship})
|
||||
: super.forElement();
|
||||
TypeHierarchyRelatedItem._forType(super.type, {required this.relationship})
|
||||
: super.forType();
|
||||
|
||||
/// An optional anchor element used to preserve type args.
|
||||
TypeHierarchyAnchor? get anchor => _anchor;
|
||||
}
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
|
||||
import 'package:analysis_server/lsp_protocol/protocol_special.dart';
|
||||
import 'package:analysis_server/lsp_protocol/protocol.dart';
|
||||
import 'package:analysis_server/src/computer/computer_lazy_type_hierarchy.dart'
|
||||
as type_hierarchy;
|
||||
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
|
||||
|
@ -14,6 +13,7 @@ import 'package:analyzer/dart/analysis/results.dart';
|
|||
import 'package:analyzer/dart/analysis/session.dart';
|
||||
import 'package:analyzer/source/line_info.dart';
|
||||
import 'package:analyzer/source/source_range.dart';
|
||||
import 'package:analyzer/src/dart/element/element.dart';
|
||||
|
||||
/// A handler for the initial "prepare" request for starting navigation with
|
||||
/// Type Hierarchy.
|
||||
|
@ -86,22 +86,20 @@ class TypeHierarchySubtypesHandler extends MessageHandler<
|
|||
MessageInfo message,
|
||||
CancellationToken token) async {
|
||||
final item = params.item;
|
||||
final data = item.data;
|
||||
final path = pathOfUri(item.uri);
|
||||
final unit = await path.mapResult(requireResolvedUnit);
|
||||
final computer = type_hierarchy.DartLazyTypeHierarchyComputer(unit.result);
|
||||
|
||||
// Convert the clients item back to one in the servers format so that we
|
||||
// can use it to get sub/super types.
|
||||
final target = toServerItem(item, unit.result.lineInfo);
|
||||
|
||||
if (target == null) {
|
||||
if (data == null) {
|
||||
return error(
|
||||
ErrorCodes.ContentModified,
|
||||
'Content was modified since Type Hierarchy node was produced',
|
||||
ErrorCodes.InvalidParams,
|
||||
'TypeHierarchyItem is missing the data field',
|
||||
);
|
||||
}
|
||||
|
||||
final calls = await computer.findSubtypes(target, server.searchEngine);
|
||||
final location = ElementLocationImpl.con2(data.ref);
|
||||
final calls = await computer.findSubtypes(location, server.searchEngine);
|
||||
final results = calls != null ? _convertItems(unit.result, calls) : null;
|
||||
return success(results);
|
||||
}
|
||||
|
@ -124,25 +122,38 @@ class TypeHierarchySupertypesHandler extends MessageHandler<
|
|||
MessageInfo message,
|
||||
CancellationToken token) async {
|
||||
final item = params.item;
|
||||
final data = item.data;
|
||||
final path = pathOfUri(item.uri);
|
||||
final unit = await path.mapResult(requireResolvedUnit);
|
||||
final computer = type_hierarchy.DartLazyTypeHierarchyComputer(unit.result);
|
||||
|
||||
// Convert the clients item back to one in the servers format so that we
|
||||
// can use it to get sub/super types.
|
||||
final target = toServerItem(item, unit.result.lineInfo);
|
||||
|
||||
if (target == null) {
|
||||
if (data == null) {
|
||||
return error(
|
||||
ErrorCodes.ContentModified,
|
||||
'Content was modified since Type Hierarchy node was produced',
|
||||
ErrorCodes.InvalidParams,
|
||||
'TypeHierarchyItem is missing the data field',
|
||||
);
|
||||
}
|
||||
|
||||
final calls = await computer.findSupertypes(target);
|
||||
final location = ElementLocationImpl.con2(data.ref);
|
||||
final anchor = _toServerAnchor(data);
|
||||
final calls = await computer.findSupertypes(location, anchor: anchor);
|
||||
final results = calls != null ? _convertItems(unit.result, calls) : null;
|
||||
return success(results);
|
||||
}
|
||||
|
||||
/// Reads the anchor from [data] (if available) and converts it to a server
|
||||
/// [type_hierarchy.TypeHierarchyAnchor].
|
||||
type_hierarchy.TypeHierarchyAnchor? _toServerAnchor(
|
||||
TypeHierarchyItemInfo data,
|
||||
) {
|
||||
final anchor = data.anchor;
|
||||
return anchor != null
|
||||
? type_hierarchy.TypeHierarchyAnchor(
|
||||
location: ElementLocationImpl.con2(anchor.ref),
|
||||
path: anchor.path,
|
||||
)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility methods used by all Type Hierarchy handlers.
|
||||
|
@ -157,6 +168,8 @@ mixin _TypeHierarchyUtils {
|
|||
type_hierarchy.TypeHierarchyItem item,
|
||||
LineInfo lineInfo,
|
||||
) {
|
||||
final anchor =
|
||||
item is type_hierarchy.TypeHierarchyRelatedItem ? item.anchor : null;
|
||||
return TypeHierarchyItem(
|
||||
name: item.displayName,
|
||||
detail: _detailFor(item),
|
||||
|
@ -164,29 +177,15 @@ mixin _TypeHierarchyUtils {
|
|||
uri: Uri.file(item.file),
|
||||
range: sourceRangeToRange(lineInfo, item.codeRange),
|
||||
selectionRange: sourceRangeToRange(lineInfo, item.nameRange),
|
||||
);
|
||||
}
|
||||
|
||||
/// Converts an LSP [TypeHierarchyItem] supplied by the client back to a
|
||||
/// server [type_hierarchy.TypeHierarchyItem] to use to look up items.
|
||||
///
|
||||
/// Returns `null` if the supplied item is no longer valid (for example its
|
||||
/// ranges are no longer valid in the current state of the document).
|
||||
type_hierarchy.TypeHierarchyItem? toServerItem(
|
||||
TypeHierarchyItem item,
|
||||
LineInfo lineInfo,
|
||||
) {
|
||||
final nameRange = toSourceRange(lineInfo, item.selectionRange);
|
||||
final codeRange = toSourceRange(lineInfo, item.range);
|
||||
if (nameRange.isError || codeRange.isError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return type_hierarchy.TypeHierarchyItem(
|
||||
displayName: item.name,
|
||||
file: item.uri.toFilePath(),
|
||||
nameRange: nameRange.result,
|
||||
codeRange: codeRange.result,
|
||||
data: TypeHierarchyItemInfo(
|
||||
ref: item.location.encoding,
|
||||
anchor: anchor != null
|
||||
? TypeHierarchyAnchor(
|
||||
ref: anchor.location.encoding,
|
||||
path: anchor.path,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -292,6 +292,39 @@ class MyCla^ss2 extends MyClass1 {}
|
|||
]));
|
||||
}
|
||||
|
||||
/// Ensure that type arguments flow across multiple levels of the tree.
|
||||
Future<void> test_generics_typeArgsFlow() async {
|
||||
final content = '''
|
||||
class A<T1, T2> {}
|
||||
class B<T1, T2> extends A<T1, T2> {}
|
||||
class C<T1> extends B<T1, String> {}
|
||||
class D extends C<int> {}
|
||||
class ^E extends D {}
|
||||
''';
|
||||
await _prepareTypeHierarchy(content);
|
||||
|
||||
// Walk the tree and collect names at each level.
|
||||
var item = prepareResult;
|
||||
var names = <String>[];
|
||||
while (item != null) {
|
||||
names.add(item.name);
|
||||
final supertypes = await typeHierarchySupertypes(item);
|
||||
item = (supertypes != null && supertypes.isNotEmpty)
|
||||
? supertypes.single
|
||||
: null;
|
||||
}
|
||||
|
||||
// Check for substituted type args.
|
||||
expect(names, [
|
||||
'E',
|
||||
'D',
|
||||
'C<int>',
|
||||
'B<int, String>',
|
||||
'A<int, String>',
|
||||
'Object',
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> test_implements() async {
|
||||
final content = '''
|
||||
/*[0*/class /*[1*/MyClass1/*1]*/ {}/*0]*/
|
||||
|
|
|
@ -97,7 +97,7 @@ class TypeHierarchyComputerFindSubtypesTest extends AbstractTypeHierarchyTest {
|
|||
TypeHierarchyItem target) async {
|
||||
final result = await getResolvedUnit(target.file);
|
||||
return DartLazyTypeHierarchyComputer(result)
|
||||
.findSubtypes(target, searchEngine);
|
||||
.findSubtypes(target.location, searchEngine);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -110,16 +110,16 @@ class TypeHierarchyComputerFindSubtypesTest extends AbstractTypeHierarchyTest {
|
|||
|
||||
Future<void> test_class_generic() async {
|
||||
final content = '''
|
||||
class My^Class1<T> {}
|
||||
/*[0*/class /*[1*/MyClass2/*1]*/ implements MyClass1<String> {}/*0]*/
|
||||
class My^Class1<T1, T2> {}
|
||||
/*[0*/class /*[1*/MyClass2/*1]*/<T1> implements MyClass1<T1, String> {}/*0]*/
|
||||
''';
|
||||
|
||||
addTestSource(content);
|
||||
final target = await findTarget();
|
||||
final supertypes = await findSubtypes(target!);
|
||||
expect(supertypes, [
|
||||
final subtypes = await findSubtypes(target!);
|
||||
expect(subtypes, [
|
||||
_isRelatedItem(
|
||||
'MyClass2',
|
||||
'MyClass2<T1>',
|
||||
testFile,
|
||||
relationship: TypeHierarchyItemRelationship.implements,
|
||||
codeRange: code.ranges[0].sourceRange,
|
||||
|
@ -136,8 +136,8 @@ class ^MyClass1 {}
|
|||
|
||||
addTestSource(content);
|
||||
final target = await findTarget();
|
||||
final supertypes = await findSubtypes(target!);
|
||||
expect(supertypes, [
|
||||
final subtypes = await findSubtypes(target!);
|
||||
expect(subtypes, [
|
||||
_isRelatedItem(
|
||||
'MyClass2',
|
||||
testFile,
|
||||
|
@ -184,8 +184,8 @@ class ^MyClass1 {}
|
|||
|
||||
addTestSource(content);
|
||||
final target = await findTarget();
|
||||
final supertypes = await findSubtypes(target!);
|
||||
expect(supertypes, [
|
||||
final subtypes = await findSubtypes(target!);
|
||||
expect(subtypes, [
|
||||
_isRelatedItem(
|
||||
'MyClass2',
|
||||
testFile,
|
||||
|
@ -287,7 +287,9 @@ class TypeHierarchyComputerFindSupertypesTest
|
|||
Future<List<TypeHierarchyItem>?> findSupertypes(
|
||||
TypeHierarchyItem target) async {
|
||||
final result = await getResolvedUnit(target.file);
|
||||
return DartLazyTypeHierarchyComputer(result).findSupertypes(target);
|
||||
final anchor = target is TypeHierarchyRelatedItem ? target.anchor : null;
|
||||
return DartLazyTypeHierarchyComputer(result)
|
||||
.findSupertypes(target.location, anchor: anchor);
|
||||
}
|
||||
|
||||
/// Test that if the file is modified between fetching a target and it's
|
||||
|
@ -318,8 +320,8 @@ class ^MyClass2 extends MyClass1 {}
|
|||
|
||||
Future<void> test_class_generic() async {
|
||||
final content = '''
|
||||
/*[0*/class /*[1*/MyClass1/*1]*/<T> {}/*0]*/
|
||||
class ^MyClass2 implements MyClass1<String> {}
|
||||
/*[0*/class /*[1*/MyClass1/*1]*/<T1, T2> {}/*0]*/
|
||||
class ^MyClass2<T1> implements MyClass1<T1, String> {}
|
||||
''';
|
||||
|
||||
addTestSource(content);
|
||||
|
@ -328,7 +330,7 @@ class ^MyClass2 implements MyClass1<String> {}
|
|||
expect(supertypes, [
|
||||
_isObject,
|
||||
_isRelatedItem(
|
||||
'MyClass1',
|
||||
'MyClass1<T1, String>',
|
||||
testFile,
|
||||
relationship: TypeHierarchyItemRelationship.implements,
|
||||
codeRange: code.ranges[0].sourceRange,
|
||||
|
@ -337,6 +339,39 @@ class ^MyClass2 implements MyClass1<String> {}
|
|||
]);
|
||||
}
|
||||
|
||||
/// Ensure that type arguments flow across multiple levels of the tree.
|
||||
Future<void> test_class_generic_typeArgsFlow() async {
|
||||
final content = '''
|
||||
class A<T1, T2> {}
|
||||
class B<T1, T2> extends A<T1, T2> {}
|
||||
class C<T1> extends B<T1, String> {}
|
||||
class D extends C<int> {}
|
||||
class ^E extends D {}
|
||||
''';
|
||||
addTestSource(content);
|
||||
|
||||
// Walk the tree and collect names at each level.
|
||||
var names = <String>[];
|
||||
var target = await findTarget();
|
||||
while (target != null) {
|
||||
names.add(target.displayName);
|
||||
final supertypes = await findSupertypes(target);
|
||||
target = (supertypes != null && supertypes.isNotEmpty)
|
||||
? supertypes.single
|
||||
: null;
|
||||
}
|
||||
|
||||
// Check for substituted type args.
|
||||
expect(names, [
|
||||
'E',
|
||||
'D',
|
||||
'C<int>',
|
||||
'B<int, String>',
|
||||
'A<int, String>',
|
||||
'Object',
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> test_class_interfaces() async {
|
||||
final content = '''
|
||||
/*[0*/class /*[1*/MyClass1/*1]*/ {}/*0]*/
|
||||
|
@ -520,6 +555,22 @@ class TypeHierarchyComputerFindTargetTest extends AbstractTypeHierarchyTest {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> test_class_generic() async {
|
||||
final content = '''
|
||||
/*[0*/class /*[1*/MyCl^ass1/*1]*/<T1, T2> {}/*0]*/
|
||||
''';
|
||||
|
||||
addTestSource(content);
|
||||
await expectTarget(
|
||||
_isItem(
|
||||
'MyClass1<T1, T2>',
|
||||
testFile,
|
||||
codeRange: code.ranges[0].sourceRange,
|
||||
nameRange: code.ranges[1].sourceRange,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_class_keyword() async {
|
||||
final content = '''
|
||||
/*[0*/cla^ss /*[1*/MyClass1/*1]*/ {
|
||||
|
|
|
@ -346,6 +346,43 @@ List<LspEntity> getCustomClasses() {
|
|||
field('message', type: 'string', canBeUndefined: true),
|
||||
],
|
||||
),
|
||||
interface(
|
||||
'TypeHierarchyAnchor',
|
||||
[
|
||||
field(
|
||||
'ref',
|
||||
type: 'string',
|
||||
comment: 'The ElementLocation for this anchor element.',
|
||||
),
|
||||
field(
|
||||
'path',
|
||||
type: 'int',
|
||||
array: true,
|
||||
comment: 'Indices used to navigate from this anchor to the element.',
|
||||
),
|
||||
],
|
||||
),
|
||||
interface(
|
||||
'TypeHierarchyItemInfo',
|
||||
[
|
||||
field(
|
||||
'ref',
|
||||
type: 'string',
|
||||
comment:
|
||||
'The ElementLocation for this element, used to re-locate the '
|
||||
'element when subtypes/supertypes are '
|
||||
'fetched later.',
|
||||
),
|
||||
field(
|
||||
'anchor',
|
||||
type: 'TypeHierarchyAnchor',
|
||||
comment:
|
||||
'An anchor element that can be used to navigate to this element '
|
||||
'preserving type arguments.',
|
||||
canBeUndefined: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
TypeAlias(
|
||||
name: 'TextDocumentEditEdits',
|
||||
baseType: ArrayType(
|
||||
|
|
|
@ -236,7 +236,10 @@ class LspMetaModelCleaner {
|
|||
},
|
||||
'TextDocumentEdit': {
|
||||
'edits': 'TextDocumentEditEdits',
|
||||
}
|
||||
},
|
||||
'TypeHierarchyItem': {
|
||||
'data': 'TypeHierarchyItemInfo',
|
||||
},
|
||||
};
|
||||
|
||||
final interface = improvedTypeMappings[interfaceName];
|
||||
|
|
Loading…
Reference in a new issue