blob: eedf10f9a4741b5c1db992452a787c5eeaedfec9 [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 TypeHierarchyComputerHelper helper;
final List<TypeHierarchyItem> _items = <TypeHierarchyItem>[];
final List<InterfaceElement> _itemClassElements = [];
final Map<Element, TypeHierarchyItem> _elementItemMap =
HashMap<Element, TypeHierarchyItem>();
TypeHierarchyComputer(this._searchEngine, Element pivotElement)
: helper = TypeHierarchyComputerHelper.fromElement(pivotElement);
/// Returns the computed type hierarchy, maybe `null`.
Future<List<TypeHierarchyItem>?> compute() async {
var pivotClass = helper.pivotClass;
if (pivotClass != null) {
_createSuperItem(pivotClass, null);
var searchEngineCache = SearchEngineCache();
await _createSubclasses(_items[0], 0, pivotClass, searchEngineCache);
return _items;
}
return null;
}
/// Returns the computed super type only type hierarchy, maybe `null`.
List<TypeHierarchyItem>? computeSuper() {
var pivotClass = helper.pivotClass;
if (pivotClass != null) {
_createSuperItem(pivotClass, null);
return _items;
}
return null;
}
Future<void> _createSubclasses(
TypeHierarchyItem item,
int itemId,
InterfaceElement classElement,
SearchEngineCache searchEngineCache,
) async {
var subElements = await getDirectSubClasses(
_searchEngine,
classElement,
searchEngineCache,
);
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 = helper.findMemberElement(subElement);
var subMemberElementDeclared = subMemberElement?.nonSynthetic2;
subItem = TypeHierarchyItem(
convertElement(subElement),
memberElement:
subMemberElementDeclared != null
? convertElement(subMemberElementDeclared)
: 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,
searchEngineCache,
);
}
}
int _createSuperItem(
InterfaceElement 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())
.join(', ');
displayName = '${classElement.displayName}<$typeArgumentsStr>';
}
var memberElement = helper.findMemberElement(classElement);
var memberElementDeclared = memberElement?.nonSynthetic2;
item = TypeHierarchyItem(
convertElement(classElement),
displayName: displayName,
memberElement:
memberElementDeclared != null
? convertElement(memberElementDeclared)
: 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.element3,
superType.typeArguments,
);
}
}
// mixins
for (var type in classElement.mixins) {
var id = _createSuperItem(type.element3, type.typeArguments);
item.mixins.add(id);
}
// interfaces
for (var type in classElement.interfaces) {
var id = _createSuperItem(type.element3, type.typeArguments);
item.interfaces.add(id);
}
// done
return itemId;
}
}
class TypeHierarchyComputerHelper {
final Element pivotElement;
final LibraryElement pivotLibrary;
final ElementKind pivotKind;
final String? pivotName;
final bool pivotFieldFinal;
final InterfaceElement? pivotClass;
TypeHierarchyComputerHelper(
this.pivotElement,
this.pivotLibrary,
this.pivotKind,
this.pivotName,
this.pivotFieldFinal,
this.pivotClass,
);
factory TypeHierarchyComputerHelper.fromElement(Element pivotElement) {
// try to find enclosing ClassElement
Element? element = pivotElement;
bool pivotFieldFinal = false;
if (pivotElement is FieldElement) {
pivotFieldFinal = pivotElement.isFinal;
element = pivotElement.enclosingElement;
}
if (pivotElement is ExecutableElement) {
element = pivotElement.enclosingElement;
}
InterfaceElement? pivotClass;
if (element is InterfaceElement) {
pivotClass = element;
}
return TypeHierarchyComputerHelper(
pivotElement,
pivotElement.library2!,
pivotElement.kind,
pivotElement.name3,
pivotFieldFinal,
pivotClass,
);
}
ExecutableElement? findMemberElement(InterfaceElement clazz) {
// Members of extension types don't override anything.
// They redeclare, and resolved statically.
if (pivotClass is ExtensionTypeElement || clazz is ExtensionTypeElement) {
return null;
}
var pivotName = this.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.isAccessibleIn2(pivotLibrary)) {
return result;
}
// try to find in the class mixin
for (var mixin in clazz.mixins.reversed) {
var mixinElement = mixin.element3;
if (pivotKind == ElementKind.METHOD) {
result = mixinElement.lookUpMethod(
name: pivotName,
library: pivotLibrary,
);
} else if (pivotKind == ElementKind.GETTER) {
result = mixinElement.lookUpGetter(
name: pivotName,
library: pivotLibrary,
);
} else if (pivotKind == ElementKind.SETTER) {
result = mixinElement.lookUpSetter(
name: pivotName,
library: pivotLibrary,
);
} else if (pivotKind == ElementKind.FIELD) {
result = mixinElement.lookUpGetter(
name: pivotName,
library: pivotLibrary,
);
if (result == null && !pivotFieldFinal) {
result = mixinElement.lookUpSetter(
name: pivotName,
library: pivotLibrary,
);
}
}
if (result == pivotElement) {
return null;
}
if (result != null) {
return result;
}
}
// not found
return null;
}
}