blob: 4877ec53553f77d31067937f19767245bd79c15d [file] [log] [blame]
// Copyright (c) 2014, 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 'dart:collection';
import 'package:analysis_server/src/protocol_server.dart'
show TypeHierarchyItem, convertElement;
import 'package:analysis_server/src/services/search/hierarchy.dart';
import 'package:analysis_server/src/services/search/search_engine.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
/// A computer for a type hierarchy of an [Element].
class TypeHierarchyComputer {
final SearchEngine _searchEngine;
final Element _pivotElement;
final LibraryElement _pivotLibrary;
final ElementKind _pivotKind;
final String? _pivotName;
late bool _pivotFieldFinal;
ClassElement? _pivotClass;
final List<TypeHierarchyItem> _items = <TypeHierarchyItem>[];
final List<ClassElement> _itemClassElements = <ClassElement>[];
final Map<Element, TypeHierarchyItem> _elementItemMap =
HashMap<Element, TypeHierarchyItem>();
TypeHierarchyComputer(this._searchEngine, this._pivotElement)
: _pivotLibrary = _pivotElement.library!,
_pivotKind = _pivotElement.kind,
_pivotName = _pivotElement.name {
// try to find enclosing ClassElement
Element? element = _pivotElement;
if (_pivotElement is FieldElement) {
_pivotFieldFinal = (_pivotElement as FieldElement).isFinal;
element = _pivotElement.enclosingElement;
}
if (_pivotElement is ExecutableElement) {
element = _pivotElement.enclosingElement;
}
if (element is ClassElement) {
_pivotClass = element;
}
}
bool get _isNonNullableByDefault => _pivotLibrary.isNonNullableByDefault;
/// Returns the computed type hierarchy, maybe `null`.
Future<List<TypeHierarchyItem>?> compute() async {
var pivotClass = _pivotClass;
if (pivotClass != null) {
_createSuperItem(pivotClass, null);
var superLength = _items.length;
await _createSubclasses(_items[0], 0, pivotClass);
// sort subclasses only
if (_items.length > superLength + 1) {
var subList = _items.sublist(superLength);
subList
.sort((a, b) => a.classElement.name.compareTo(b.classElement.name));
for (var i = 0; i < subList.length; i++) {
_items[i + superLength] = subList[i];
}
}
return _items;
}
return null;
}
/// Returns the computed super type only type hierarchy, maybe `null`.
List<TypeHierarchyItem>? computeSuper() {
var pivotClass = _pivotClass;
if (pivotClass != null) {
_createSuperItem(pivotClass, null);
return _items;
}
return null;
}
Future _createSubclasses(
TypeHierarchyItem item, int itemId, ClassElement classElement) async {
var subElements = await getDirectSubClasses(_searchEngine, classElement);
var subItemIds = <int>[];
for (var subElement in subElements) {
// check for recursion
var subItem = _elementItemMap[subElement];
if (subItem != null) {
var id = _items.indexOf(subItem);
item.subclasses.add(id);
continue;
}
// create a subclass item
var subMemberElement = _findMemberElement(subElement);
var subMemberElementDeclared = subMemberElement?.nonSynthetic;
subItem = TypeHierarchyItem(
convertElement(subElement, withNullability: _isNonNullableByDefault),
memberElement: subMemberElementDeclared != null
? convertElement(subMemberElementDeclared,
withNullability: _isNonNullableByDefault)
: null,
superclass: itemId);
var subItemId = _items.length;
// remember
_elementItemMap[subElement] = subItem;
_items.add(subItem);
_itemClassElements.add(subElement);
// add to hierarchy
item.subclasses.add(subItemId);
subItemIds.add(subItemId);
}
// compute subclasses of subclasses
for (var subItemId in subItemIds) {
var subItem = _items[subItemId];
var subItemElement = _itemClassElements[subItemId];
await _createSubclasses(subItem, subItemId, subItemElement);
}
}
int _createSuperItem(
ClassElement classElement, List<DartType>? typeArguments) {
// check for recursion
var cachedItem = _elementItemMap[classElement];
if (cachedItem != null) {
return _items.indexOf(cachedItem);
}
// create an empty item now
TypeHierarchyItem item;
int itemId;
{
String? displayName;
if (typeArguments != null && typeArguments.isNotEmpty) {
var typeArgumentsStr = typeArguments
.map((type) =>
type.getDisplayString(withNullability: _isNonNullableByDefault))
.join(', ');
displayName = classElement.displayName + '<' + typeArgumentsStr + '>';
}
var memberElement = _findMemberElement(classElement);
var memberElementDeclared = memberElement?.nonSynthetic;
item = TypeHierarchyItem(
convertElement(classElement,
withNullability: _isNonNullableByDefault),
displayName: displayName,
memberElement: memberElementDeclared != null
? convertElement(memberElementDeclared,
withNullability: _isNonNullableByDefault)
: null);
_elementItemMap[classElement] = item;
itemId = _items.length;
_items.add(item);
_itemClassElements.add(classElement);
}
// superclass
{
var superType = classElement.supertype;
if (superType != null) {
item.superclass = _createSuperItem(
superType.element,
superType.typeArguments,
);
}
}
// mixins
classElement.mixins.forEach((InterfaceType type) {
var id = _createSuperItem(type.element, type.typeArguments);
item.mixins.add(id);
});
// interfaces
classElement.interfaces.forEach((InterfaceType type) {
var id = _createSuperItem(type.element, type.typeArguments);
item.interfaces.add(id);
});
// done
return itemId;
}
ExecutableElement? _findMemberElement(ClassElement clazz) {
var pivotName = _pivotName;
if (pivotName == null) {
return null;
}
ExecutableElement? result;
// try to find in the class itself
if (_pivotKind == ElementKind.METHOD) {
result = clazz.getMethod(pivotName);
} else if (_pivotKind == ElementKind.GETTER) {
result = clazz.getGetter(pivotName);
} else if (_pivotKind == ElementKind.SETTER) {
result = clazz.getSetter(pivotName);
} else if (_pivotKind == ElementKind.FIELD) {
result = clazz.getGetter(pivotName);
if (result == null && !_pivotFieldFinal) {
result = clazz.getSetter(pivotName);
}
}
if (result != null && result.isAccessibleIn(_pivotLibrary)) {
return result;
}
// try to find in the class mixin
for (var mixin in clazz.mixins.reversed) {
var mixinElement = mixin.element;
if (_pivotKind == ElementKind.METHOD) {
result = mixinElement.lookUpMethod(pivotName, _pivotLibrary);
} else if (_pivotKind == ElementKind.GETTER) {
result = mixinElement.lookUpGetter(pivotName, _pivotLibrary);
} else if (_pivotKind == ElementKind.SETTER) {
result = mixinElement.lookUpSetter(pivotName, _pivotLibrary);
} else if (_pivotKind == ElementKind.FIELD) {
result = mixinElement.lookUpGetter(pivotName, _pivotLibrary);
if (result == null && !_pivotFieldFinal) {
result = mixinElement.lookUpSetter(pivotName, _pivotLibrary);
}
}
if (result == _pivotElement) {
return null;
}
if (result != null) {
return result;
}
}
// not found
return null;
}
}