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());
|
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 {
|
class ValidateRefactorResult implements ToJsonable {
|
||||||
static const jsonHandler = LspJsonHandler(
|
static const jsonHandler = LspJsonHandler(
|
||||||
ValidateRefactorResult.canParse,
|
ValidateRefactorResult.canParse,
|
||||||
|
@ -2184,6 +2325,32 @@ bool _canParseListFlutterOutlineAttribute(
|
||||||
return true;
|
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(
|
bool _canParseListOutline(
|
||||||
Map<String, Object?> map, LspJsonReporter reporter, String fieldName,
|
Map<String, Object?> map, LspJsonReporter reporter, String fieldName,
|
||||||
{required bool allowsUndefined, required bool allowsNull}) {
|
{required bool allowsUndefined, required bool allowsNull}) {
|
||||||
|
@ -2395,6 +2562,32 @@ bool _canParseString(
|
||||||
return true;
|
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(
|
bool _canParseUri(
|
||||||
Map<String, Object?> map, LspJsonReporter reporter, String fieldName,
|
Map<String, Object?> map, LspJsonReporter reporter, String fieldName,
|
||||||
{required bool allowsUndefined, required bool allowsNull}) {
|
{required bool allowsUndefined, required bool allowsNull}) {
|
||||||
|
|
|
@ -31448,7 +31448,9 @@ class TypeHierarchyItem implements ToJsonable {
|
||||||
});
|
});
|
||||||
static TypeHierarchyItem fromJson(Map<String, Object?> json) {
|
static TypeHierarchyItem fromJson(Map<String, Object?> json) {
|
||||||
final dataJson = json['data'];
|
final dataJson = json['data'];
|
||||||
final data = dataJson;
|
final data = dataJson != null
|
||||||
|
? TypeHierarchyItemInfo.fromJson(dataJson as Map<String, Object?>)
|
||||||
|
: null;
|
||||||
final detailJson = json['detail'];
|
final detailJson = json['detail'];
|
||||||
final detail = detailJson as String?;
|
final detail = detailJson as String?;
|
||||||
final kindJson = json['kind'];
|
final kindJson = json['kind'];
|
||||||
|
@ -31482,7 +31484,7 @@ class TypeHierarchyItem implements ToJsonable {
|
||||||
/// supertypes or subtypes requests. It could also be used to identify the
|
/// supertypes or subtypes requests. It could also be used to identify the
|
||||||
/// type hierarchy in the server, helping improve the performance on resolving
|
/// type hierarchy in the server, helping improve the performance on resolving
|
||||||
/// supertypes and subtypes.
|
/// supertypes and subtypes.
|
||||||
final LSPAny data;
|
final TypeHierarchyItemInfo? data;
|
||||||
|
|
||||||
/// More detail for this item, e.g. the signature of a function.
|
/// More detail for this item, e.g. the signature of a function.
|
||||||
final String? detail;
|
final String? detail;
|
||||||
|
@ -31511,7 +31513,7 @@ class TypeHierarchyItem implements ToJsonable {
|
||||||
Map<String, Object?> toJson() {
|
Map<String, Object?> toJson() {
|
||||||
var result = <String, Object?>{};
|
var result = <String, Object?>{};
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
result['data'] = data;
|
result['data'] = data?.toJson();
|
||||||
}
|
}
|
||||||
if (detail != null) {
|
if (detail != null) {
|
||||||
result['detail'] = detail;
|
result['detail'] = detail;
|
||||||
|
@ -31529,6 +31531,10 @@ class TypeHierarchyItem implements ToJsonable {
|
||||||
|
|
||||||
static bool canParse(Object? obj, LspJsonReporter reporter) {
|
static bool canParse(Object? obj, LspJsonReporter reporter) {
|
||||||
if (obj is Map<String, Object?>) {
|
if (obj is Map<String, Object?>) {
|
||||||
|
if (!_canParseTypeHierarchyItemInfo(obj, reporter, 'data',
|
||||||
|
allowsUndefined: true, allowsNull: false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!_canParseString(obj, reporter, 'detail',
|
if (!_canParseString(obj, reporter, 'detail',
|
||||||
allowsUndefined: true, allowsNull: false)) {
|
allowsUndefined: true, allowsNull: false)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -41981,6 +41987,32 @@ bool _canParseTypeHierarchyItem(
|
||||||
return true;
|
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(
|
bool _canParseUniquenessLevel(
|
||||||
Map<String, Object?> map, LspJsonReporter reporter, String fieldName,
|
Map<String, Object?> map, LspJsonReporter reporter, String fieldName,
|
||||||
{required bool allowsUndefined, required bool allowsNull}) {
|
{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/source/source_range.dart';
|
||||||
import 'package:analyzer/src/dart/ast/utilities.dart';
|
import 'package:analyzer/src/dart/ast/utilities.dart';
|
||||||
import 'package:analyzer/src/dart/element/element.dart';
|
import 'package:analyzer/src/dart/element/element.dart';
|
||||||
|
import 'package:analyzer/src/utilities/extensions/analysis_session.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
/// A lazy computer for Type Hierarchies.
|
/// A lazy computer for Type Hierarchies.
|
||||||
|
@ -32,12 +33,12 @@ class DartLazyTypeHierarchyComputer {
|
||||||
|
|
||||||
DartLazyTypeHierarchyComputer(this._result);
|
DartLazyTypeHierarchyComputer(this._result);
|
||||||
|
|
||||||
/// Finds subtypes for [target].
|
/// Finds subtypes for [Element] at [location].
|
||||||
Future<List<TypeHierarchyRelatedItem>?> findSubtypes(
|
Future<List<TypeHierarchyRelatedItem>?> findSubtypes(
|
||||||
TypeHierarchyItem target,
|
ElementLocation location,
|
||||||
SearchEngine searchEngine,
|
SearchEngine searchEngine,
|
||||||
) async {
|
) async {
|
||||||
final targetElement = _findTargetElement(target);
|
final targetElement = await _findTargetElement(location);
|
||||||
if (targetElement is! InterfaceElement) {
|
if (targetElement is! InterfaceElement) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -45,16 +46,37 @@ class DartLazyTypeHierarchyComputer {
|
||||||
return _getSubtypes(targetElement, searchEngine);
|
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(
|
Future<List<TypeHierarchyRelatedItem>?> findSupertypes(
|
||||||
TypeHierarchyItem target,
|
ElementLocation location, {
|
||||||
) async {
|
TypeHierarchyAnchor? anchor,
|
||||||
final targetElement = _findTargetElement(target);
|
}) async {
|
||||||
|
var targetElement = await _findTargetElement(location);
|
||||||
if (targetElement == null) {
|
if (targetElement == null) {
|
||||||
return 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].
|
/// Finds a target for starting type hierarchy navigation at [offset].
|
||||||
|
@ -74,19 +96,15 @@ class DartLazyTypeHierarchyComputer {
|
||||||
element = declaration?.declaredElement;
|
element = declaration?.declaredElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
return element != null ? TypeHierarchyItem.forElement(element) : null;
|
return element is InterfaceElement
|
||||||
|
? TypeHierarchyItem.forType(element.thisType)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Element? _findTargetElement(TypeHierarchyItem target) {
|
/// Locate the [Element] referenced by [location].
|
||||||
assert(target.file == _result.path);
|
Future<InterfaceElement?> _findTargetElement(ElementLocation location) async {
|
||||||
// Locate the element by name instead of offset since this call my occur
|
final element = await _result.session.locateElement(location);
|
||||||
// much later than the original find target request and it's possible the
|
return element is InterfaceElement ? element : null;
|
||||||
// user has made changes since.
|
|
||||||
final targetElement = _result.unit.declarations
|
|
||||||
.where(_isValidTargetDeclaration)
|
|
||||||
.map((declaration) => declaration.declaredElement)
|
|
||||||
.firstWhereOrNull((element) => element?.name == target.displayName);
|
|
||||||
return targetElement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets immediate sub types for the class/mixin [element].
|
/// Gets immediate sub types for the class/mixin [element].
|
||||||
|
@ -129,20 +147,36 @@ class DartLazyTypeHierarchyComputer {
|
||||||
/// Includes all elements that contribute implementation to the type
|
/// Includes all elements that contribute implementation to the type
|
||||||
/// such as supertypes and mixins, but not interfaces, constraints or
|
/// such as supertypes and mixins, but not interfaces, constraints or
|
||||||
/// extended types.
|
/// extended types.
|
||||||
List<TypeHierarchyRelatedItem> _getSupertypes(Element element) {
|
List<TypeHierarchyRelatedItem> _getSupertypes(
|
||||||
final supertype = element is InterfaceElement ? element.supertype : null;
|
InterfaceType type, {
|
||||||
final interfaces =
|
TypeHierarchyAnchor? anchor,
|
||||||
element is InterfaceOrAugmentationElement ? element.interfaces : null;
|
}) {
|
||||||
final mixins =
|
final supertype = type.superclass;
|
||||||
element is InterfaceOrAugmentationElement ? element.mixins : null;
|
final interfaces = type.interfaces;
|
||||||
final superclassConstraints =
|
final mixins = type.mixins;
|
||||||
element is MixinElement ? element.superclassConstraints : null;
|
final superclassConstraints = type.superclassConstraints;
|
||||||
return [
|
|
||||||
|
final supertypes = [
|
||||||
if (supertype != null) TypeHierarchyRelatedItem.extends_(supertype),
|
if (supertype != null) TypeHierarchyRelatedItem.extends_(supertype),
|
||||||
...?superclassConstraints?.map(TypeHierarchyRelatedItem.constrainedTo),
|
...superclassConstraints.map(TypeHierarchyRelatedItem.constrainedTo),
|
||||||
...?interfaces?.map(TypeHierarchyRelatedItem.implements),
|
...interfaces.map(TypeHierarchyRelatedItem.implements),
|
||||||
...?mixins?.map(TypeHierarchyRelatedItem.mixesIn),
|
...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
|
/// Returns whether [declaration] is a valid target for type hierarchy
|
||||||
|
@ -151,6 +185,37 @@ class DartLazyTypeHierarchyComputer {
|
||||||
declaration is ClassDeclaration ||
|
declaration is ClassDeclaration ||
|
||||||
declaration is MixinDeclaration ||
|
declaration is MixinDeclaration ||
|
||||||
declaration is EnumDeclaration;
|
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.
|
/// 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.
|
/// The user-visible name for this item in the Type Hierarchy.
|
||||||
final String displayName;
|
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.
|
/// The file that contains the declaration of this item.
|
||||||
final String file;
|
final String file;
|
||||||
|
|
||||||
|
@ -168,17 +244,23 @@ class TypeHierarchyItem {
|
||||||
final SourceRange codeRange;
|
final SourceRange codeRange;
|
||||||
|
|
||||||
TypeHierarchyItem({
|
TypeHierarchyItem({
|
||||||
|
required InterfaceType type,
|
||||||
required this.displayName,
|
required this.displayName,
|
||||||
|
required this.location,
|
||||||
required this.file,
|
required this.file,
|
||||||
required this.nameRange,
|
required this.nameRange,
|
||||||
required this.codeRange,
|
required this.codeRange,
|
||||||
});
|
}) : _type = type;
|
||||||
|
|
||||||
TypeHierarchyItem.forElement(Element element)
|
TypeHierarchyItem.forType(InterfaceType type)
|
||||||
: displayName = element.displayName,
|
: this(
|
||||||
nameRange = _nameRangeForElement(element),
|
type: type,
|
||||||
codeRange = _codeRangeForElement(element),
|
displayName: _displayNameForType(type),
|
||||||
file = element.source!.fullName;
|
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].
|
/// Returns the [SourceRange] of the code for [element].
|
||||||
static SourceRange _codeRangeForElement(Element element) {
|
static SourceRange _codeRangeForElement(Element element) {
|
||||||
|
@ -187,6 +269,11 @@ class TypeHierarchyItem {
|
||||||
return SourceRange(elementImpl.codeOffset!, elementImpl.codeLength!);
|
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].
|
/// Returns the [SourceRange] of the name for [element].
|
||||||
static SourceRange _nameRangeForElement(Element element) {
|
static SourceRange _nameRangeForElement(Element element) {
|
||||||
element = element.nonSynthetic;
|
element = element.nonSynthetic;
|
||||||
|
@ -208,32 +295,35 @@ enum TypeHierarchyItemRelationship {
|
||||||
mixesIn,
|
mixesIn,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A supertype of subtype of a [TypeHierarchyItem].
|
/// A supertype or subtype of a [TypeHierarchyItem].
|
||||||
class TypeHierarchyRelatedItem extends TypeHierarchyItem {
|
class TypeHierarchyRelatedItem extends TypeHierarchyItem {
|
||||||
/// The relationship this item has with the target item.
|
/// The relationship this item has with the target item.
|
||||||
final TypeHierarchyItemRelationship relationship;
|
final TypeHierarchyItemRelationship relationship;
|
||||||
|
|
||||||
TypeHierarchyRelatedItem.constrainedTo(InterfaceType type)
|
TypeHierarchyAnchor? _anchor;
|
||||||
: this._forElement(type.element,
|
|
||||||
relationship: TypeHierarchyItemRelationship.constrainedTo);
|
|
||||||
|
|
||||||
|
TypeHierarchyRelatedItem.constrainedTo(InterfaceType type)
|
||||||
|
: this._forType(type,
|
||||||
|
relationship: TypeHierarchyItemRelationship.constrainedTo);
|
||||||
TypeHierarchyRelatedItem.extends_(InterfaceType type)
|
TypeHierarchyRelatedItem.extends_(InterfaceType type)
|
||||||
: this._forElement(type.element,
|
: this._forType(type,
|
||||||
relationship: TypeHierarchyItemRelationship.extends_);
|
relationship: TypeHierarchyItemRelationship.extends_);
|
||||||
|
|
||||||
TypeHierarchyRelatedItem.implements(InterfaceType type)
|
TypeHierarchyRelatedItem.implements(InterfaceType type)
|
||||||
: this._forElement(type.element,
|
: this._forType(type,
|
||||||
relationship: TypeHierarchyItemRelationship.implements);
|
relationship: TypeHierarchyItemRelationship.implements);
|
||||||
|
|
||||||
TypeHierarchyRelatedItem.mixesIn(InterfaceType type)
|
TypeHierarchyRelatedItem.mixesIn(InterfaceType type)
|
||||||
: this._forElement(type.element,
|
: this._forType(type,
|
||||||
relationship: TypeHierarchyItemRelationship.mixesIn);
|
relationship: TypeHierarchyItemRelationship.mixesIn);
|
||||||
|
|
||||||
TypeHierarchyRelatedItem.unknown(InterfaceType type)
|
TypeHierarchyRelatedItem.unknown(InterfaceType type)
|
||||||
: this._forElement(type.element,
|
: this._forType(type,
|
||||||
relationship: TypeHierarchyItemRelationship.unknown);
|
relationship: TypeHierarchyItemRelationship.unknown);
|
||||||
|
|
||||||
TypeHierarchyRelatedItem._forElement(super.element,
|
TypeHierarchyRelatedItem._forType(super.type, {required this.relationship})
|
||||||
{required this.relationship})
|
: super.forType();
|
||||||
: super.forElement();
|
|
||||||
|
/// An optional anchor element used to preserve type args.
|
||||||
|
TypeHierarchyAnchor? get anchor => _anchor;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
|
import 'package:analysis_server/lsp_protocol/protocol.dart';
|
||||||
import 'package:analysis_server/lsp_protocol/protocol_special.dart';
|
|
||||||
import 'package:analysis_server/src/computer/computer_lazy_type_hierarchy.dart'
|
import 'package:analysis_server/src/computer/computer_lazy_type_hierarchy.dart'
|
||||||
as type_hierarchy;
|
as type_hierarchy;
|
||||||
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
|
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/dart/analysis/session.dart';
|
||||||
import 'package:analyzer/source/line_info.dart';
|
import 'package:analyzer/source/line_info.dart';
|
||||||
import 'package:analyzer/source/source_range.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
|
/// A handler for the initial "prepare" request for starting navigation with
|
||||||
/// Type Hierarchy.
|
/// Type Hierarchy.
|
||||||
|
@ -86,22 +86,20 @@ class TypeHierarchySubtypesHandler extends MessageHandler<
|
||||||
MessageInfo message,
|
MessageInfo message,
|
||||||
CancellationToken token) async {
|
CancellationToken token) async {
|
||||||
final item = params.item;
|
final item = params.item;
|
||||||
|
final data = item.data;
|
||||||
final path = pathOfUri(item.uri);
|
final path = pathOfUri(item.uri);
|
||||||
final unit = await path.mapResult(requireResolvedUnit);
|
final unit = await path.mapResult(requireResolvedUnit);
|
||||||
final computer = type_hierarchy.DartLazyTypeHierarchyComputer(unit.result);
|
final computer = type_hierarchy.DartLazyTypeHierarchyComputer(unit.result);
|
||||||
|
|
||||||
// Convert the clients item back to one in the servers format so that we
|
if (data == null) {
|
||||||
// can use it to get sub/super types.
|
|
||||||
final target = toServerItem(item, unit.result.lineInfo);
|
|
||||||
|
|
||||||
if (target == null) {
|
|
||||||
return error(
|
return error(
|
||||||
ErrorCodes.ContentModified,
|
ErrorCodes.InvalidParams,
|
||||||
'Content was modified since Type Hierarchy node was produced',
|
'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;
|
final results = calls != null ? _convertItems(unit.result, calls) : null;
|
||||||
return success(results);
|
return success(results);
|
||||||
}
|
}
|
||||||
|
@ -124,25 +122,38 @@ class TypeHierarchySupertypesHandler extends MessageHandler<
|
||||||
MessageInfo message,
|
MessageInfo message,
|
||||||
CancellationToken token) async {
|
CancellationToken token) async {
|
||||||
final item = params.item;
|
final item = params.item;
|
||||||
|
final data = item.data;
|
||||||
final path = pathOfUri(item.uri);
|
final path = pathOfUri(item.uri);
|
||||||
final unit = await path.mapResult(requireResolvedUnit);
|
final unit = await path.mapResult(requireResolvedUnit);
|
||||||
final computer = type_hierarchy.DartLazyTypeHierarchyComputer(unit.result);
|
final computer = type_hierarchy.DartLazyTypeHierarchyComputer(unit.result);
|
||||||
|
|
||||||
// Convert the clients item back to one in the servers format so that we
|
if (data == null) {
|
||||||
// can use it to get sub/super types.
|
|
||||||
final target = toServerItem(item, unit.result.lineInfo);
|
|
||||||
|
|
||||||
if (target == null) {
|
|
||||||
return error(
|
return error(
|
||||||
ErrorCodes.ContentModified,
|
ErrorCodes.InvalidParams,
|
||||||
'Content was modified since Type Hierarchy node was produced',
|
'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;
|
final results = calls != null ? _convertItems(unit.result, calls) : null;
|
||||||
return success(results);
|
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.
|
/// Utility methods used by all Type Hierarchy handlers.
|
||||||
|
@ -157,6 +168,8 @@ mixin _TypeHierarchyUtils {
|
||||||
type_hierarchy.TypeHierarchyItem item,
|
type_hierarchy.TypeHierarchyItem item,
|
||||||
LineInfo lineInfo,
|
LineInfo lineInfo,
|
||||||
) {
|
) {
|
||||||
|
final anchor =
|
||||||
|
item is type_hierarchy.TypeHierarchyRelatedItem ? item.anchor : null;
|
||||||
return TypeHierarchyItem(
|
return TypeHierarchyItem(
|
||||||
name: item.displayName,
|
name: item.displayName,
|
||||||
detail: _detailFor(item),
|
detail: _detailFor(item),
|
||||||
|
@ -164,29 +177,15 @@ mixin _TypeHierarchyUtils {
|
||||||
uri: Uri.file(item.file),
|
uri: Uri.file(item.file),
|
||||||
range: sourceRangeToRange(lineInfo, item.codeRange),
|
range: sourceRangeToRange(lineInfo, item.codeRange),
|
||||||
selectionRange: sourceRangeToRange(lineInfo, item.nameRange),
|
selectionRange: sourceRangeToRange(lineInfo, item.nameRange),
|
||||||
);
|
data: TypeHierarchyItemInfo(
|
||||||
}
|
ref: item.location.encoding,
|
||||||
|
anchor: anchor != null
|
||||||
/// Converts an LSP [TypeHierarchyItem] supplied by the client back to a
|
? TypeHierarchyAnchor(
|
||||||
/// server [type_hierarchy.TypeHierarchyItem] to use to look up items.
|
ref: anchor.location.encoding,
|
||||||
///
|
path: anchor.path,
|
||||||
/// 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).
|
: null,
|
||||||
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,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
Future<void> test_implements() async {
|
||||||
final content = '''
|
final content = '''
|
||||||
/*[0*/class /*[1*/MyClass1/*1]*/ {}/*0]*/
|
/*[0*/class /*[1*/MyClass1/*1]*/ {}/*0]*/
|
||||||
|
|
|
@ -97,7 +97,7 @@ class TypeHierarchyComputerFindSubtypesTest extends AbstractTypeHierarchyTest {
|
||||||
TypeHierarchyItem target) async {
|
TypeHierarchyItem target) async {
|
||||||
final result = await getResolvedUnit(target.file);
|
final result = await getResolvedUnit(target.file);
|
||||||
return DartLazyTypeHierarchyComputer(result)
|
return DartLazyTypeHierarchyComputer(result)
|
||||||
.findSubtypes(target, searchEngine);
|
.findSubtypes(target.location, searchEngine);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -110,16 +110,16 @@ class TypeHierarchyComputerFindSubtypesTest extends AbstractTypeHierarchyTest {
|
||||||
|
|
||||||
Future<void> test_class_generic() async {
|
Future<void> test_class_generic() async {
|
||||||
final content = '''
|
final content = '''
|
||||||
class My^Class1<T> {}
|
class My^Class1<T1, T2> {}
|
||||||
/*[0*/class /*[1*/MyClass2/*1]*/ implements MyClass1<String> {}/*0]*/
|
/*[0*/class /*[1*/MyClass2/*1]*/<T1> implements MyClass1<T1, String> {}/*0]*/
|
||||||
''';
|
''';
|
||||||
|
|
||||||
addTestSource(content);
|
addTestSource(content);
|
||||||
final target = await findTarget();
|
final target = await findTarget();
|
||||||
final supertypes = await findSubtypes(target!);
|
final subtypes = await findSubtypes(target!);
|
||||||
expect(supertypes, [
|
expect(subtypes, [
|
||||||
_isRelatedItem(
|
_isRelatedItem(
|
||||||
'MyClass2',
|
'MyClass2<T1>',
|
||||||
testFile,
|
testFile,
|
||||||
relationship: TypeHierarchyItemRelationship.implements,
|
relationship: TypeHierarchyItemRelationship.implements,
|
||||||
codeRange: code.ranges[0].sourceRange,
|
codeRange: code.ranges[0].sourceRange,
|
||||||
|
@ -136,8 +136,8 @@ class ^MyClass1 {}
|
||||||
|
|
||||||
addTestSource(content);
|
addTestSource(content);
|
||||||
final target = await findTarget();
|
final target = await findTarget();
|
||||||
final supertypes = await findSubtypes(target!);
|
final subtypes = await findSubtypes(target!);
|
||||||
expect(supertypes, [
|
expect(subtypes, [
|
||||||
_isRelatedItem(
|
_isRelatedItem(
|
||||||
'MyClass2',
|
'MyClass2',
|
||||||
testFile,
|
testFile,
|
||||||
|
@ -184,8 +184,8 @@ class ^MyClass1 {}
|
||||||
|
|
||||||
addTestSource(content);
|
addTestSource(content);
|
||||||
final target = await findTarget();
|
final target = await findTarget();
|
||||||
final supertypes = await findSubtypes(target!);
|
final subtypes = await findSubtypes(target!);
|
||||||
expect(supertypes, [
|
expect(subtypes, [
|
||||||
_isRelatedItem(
|
_isRelatedItem(
|
||||||
'MyClass2',
|
'MyClass2',
|
||||||
testFile,
|
testFile,
|
||||||
|
@ -287,7 +287,9 @@ class TypeHierarchyComputerFindSupertypesTest
|
||||||
Future<List<TypeHierarchyItem>?> findSupertypes(
|
Future<List<TypeHierarchyItem>?> findSupertypes(
|
||||||
TypeHierarchyItem target) async {
|
TypeHierarchyItem target) async {
|
||||||
final result = await getResolvedUnit(target.file);
|
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
|
/// 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 {
|
Future<void> test_class_generic() async {
|
||||||
final content = '''
|
final content = '''
|
||||||
/*[0*/class /*[1*/MyClass1/*1]*/<T> {}/*0]*/
|
/*[0*/class /*[1*/MyClass1/*1]*/<T1, T2> {}/*0]*/
|
||||||
class ^MyClass2 implements MyClass1<String> {}
|
class ^MyClass2<T1> implements MyClass1<T1, String> {}
|
||||||
''';
|
''';
|
||||||
|
|
||||||
addTestSource(content);
|
addTestSource(content);
|
||||||
|
@ -328,7 +330,7 @@ class ^MyClass2 implements MyClass1<String> {}
|
||||||
expect(supertypes, [
|
expect(supertypes, [
|
||||||
_isObject,
|
_isObject,
|
||||||
_isRelatedItem(
|
_isRelatedItem(
|
||||||
'MyClass1',
|
'MyClass1<T1, String>',
|
||||||
testFile,
|
testFile,
|
||||||
relationship: TypeHierarchyItemRelationship.implements,
|
relationship: TypeHierarchyItemRelationship.implements,
|
||||||
codeRange: code.ranges[0].sourceRange,
|
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 {
|
Future<void> test_class_interfaces() async {
|
||||||
final content = '''
|
final content = '''
|
||||||
/*[0*/class /*[1*/MyClass1/*1]*/ {}/*0]*/
|
/*[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 {
|
Future<void> test_class_keyword() async {
|
||||||
final content = '''
|
final content = '''
|
||||||
/*[0*/cla^ss /*[1*/MyClass1/*1]*/ {
|
/*[0*/cla^ss /*[1*/MyClass1/*1]*/ {
|
||||||
|
|
|
@ -346,6 +346,43 @@ List<LspEntity> getCustomClasses() {
|
||||||
field('message', type: 'string', canBeUndefined: true),
|
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(
|
TypeAlias(
|
||||||
name: 'TextDocumentEditEdits',
|
name: 'TextDocumentEditEdits',
|
||||||
baseType: ArrayType(
|
baseType: ArrayType(
|
||||||
|
|
|
@ -236,7 +236,10 @@ class LspMetaModelCleaner {
|
||||||
},
|
},
|
||||||
'TextDocumentEdit': {
|
'TextDocumentEdit': {
|
||||||
'edits': 'TextDocumentEditEdits',
|
'edits': 'TextDocumentEditEdits',
|
||||||
}
|
},
|
||||||
|
'TypeHierarchyItem': {
|
||||||
|
'data': 'TypeHierarchyItemInfo',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
final interface = improvedTypeMappings[interfaceName];
|
final interface = improvedTypeMappings[interfaceName];
|
||||||
|
|
Loading…
Reference in a new issue