blob: a647d29c48b8b4b6ebf6944f798370dc60a07194 [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:dartdoc/src/element_type.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/model_utils.dart' as model_utils;
import 'package:quiver/iterables.dart' as quiver;
class Class extends Container
with TypeParameters, Categorization
implements EnclosedElement {
List<DefinedElementType> mixins;
DefinedElementType supertype;
List<DefinedElementType> _interfaces;
List<Constructor> _constructors;
List<Operator> _inheritedOperators;
List<Method> _inheritedMethods;
List<Field> _inheritedProperties;
Class(ClassElement element, Library library, PackageGraph packageGraph)
: super(element, library, packageGraph) {
packageGraph.specialClasses.addSpecial(this);
mixins = _cls.mixins
.map((f) {
DefinedElementType t = ElementType.from(f, library, packageGraph);
return t;
})
.where((mixin) => mixin != null)
.toList(growable: false);
if (_cls.supertype != null && _cls.supertype.element.supertype != null) {
supertype = ElementType.from(_cls.supertype, library, packageGraph);
}
_interfaces = _cls.interfaces
.map((f) =>
ElementType.from(f, library, packageGraph) as DefinedElementType)
.toList(growable: false);
}
Constructor _defaultConstructor;
Constructor get defaultConstructor {
if (_defaultConstructor == null) {
_defaultConstructor = constructors
.firstWhere((c) => c.isDefaultConstructor, orElse: () => null);
}
return _defaultConstructor;
}
bool get hasPotentiallyApplicableExtensions =>
potentiallyApplicableExtensions.isNotEmpty;
List<Extension> _potentiallyApplicableExtensions;
Iterable<Extension> get potentiallyApplicableExtensions {
if (_potentiallyApplicableExtensions == null) {
_potentiallyApplicableExtensions = model_utils
.filterNonDocumented(packageGraph.extensions)
.where((e) => e.couldApplyTo(this))
.toList(growable: false)
..sort(byName);
}
return _potentiallyApplicableExtensions;
}
Iterable<Method> get allInstanceMethods =>
quiver.concat([instanceMethods, inheritedMethods]);
@override
Iterable<Method> get allPublicInstanceMethods =>
model_utils.filterNonPublic(allInstanceMethods);
bool get allPublicInstanceMethodsInherited =>
instanceMethods.every((f) => f.isInherited);
@override
Iterable<Field> get allInstanceFields =>
quiver.concat([instanceProperties, inheritedProperties]);
bool get allPublicInstancePropertiesInherited =>
allPublicInstanceProperties.every((f) => f.isInherited);
@override
Iterable<Operator> get allOperators =>
quiver.concat([operators, inheritedOperators]);
bool get allPublicOperatorsInherited =>
allPublicOperators.every((f) => f.isInherited);
Map<Element, ModelElement> _allElements;
Map<Element, ModelElement> get allElements {
if (_allElements == null) {
_allElements = Map();
for (ModelElement me in allModelElements) {
assert(!_allElements.containsKey(me.element));
_allElements[me.element] = me;
}
}
return _allElements;
}
Map<String, List<ModelElement>> _allModelElementsByNamePart;
/// Helper for [_MarkdownCommentReference._getResultsForClass].
Map<String, List<ModelElement>> get allModelElementsByNamePart {
if (_allModelElementsByNamePart == null) {
_allModelElementsByNamePart = {};
for (ModelElement me in allModelElements) {
_allModelElementsByNamePart.update(
me.namePart, (List<ModelElement> v) => v..add(me),
ifAbsent: () => <ModelElement>[me]);
}
}
return _allModelElementsByNamePart;
}
/// This class might be canonical for elements it does not contain.
/// See [Inheritable.canonicalEnclosingContainer].
bool contains(Element element) => allElements.containsKey(element);
Map<String, List<ModelElement>> _membersByName;
/// Given a ModelElement that is a member of some other class, return
/// a member of this class that has the same name and return type.
///
/// This enables object substitution for canonicalization, such as Interceptor
/// for Object.
ModelElement memberByExample(ModelElement example) {
if (_membersByName == null) {
_membersByName = Map();
for (ModelElement me in allModelElements) {
if (!_membersByName.containsKey(me.name)) {
_membersByName[me.name] = List();
}
_membersByName[me.name].add(me);
}
}
ModelElement member;
Iterable<ModelElement> possibleMembers = _membersByName[example.name]
.where((e) => e.runtimeType == example.runtimeType);
if (example.runtimeType == Accessor) {
possibleMembers = possibleMembers.where(
(e) => (example as Accessor).isGetter == (e as Accessor).isGetter);
}
member = possibleMembers.first;
assert(possibleMembers.length == 1);
return member;
}
List<ModelElement> _allModelElements;
List<ModelElement> get allModelElements {
if (_allModelElements == null) {
_allModelElements = List.from(
quiver.concat([
allInstanceMethods,
allInstanceFields,
allAccessors,
allOperators,
constants,
constructors,
staticMethods,
staticProperties,
typeParameters,
]),
growable: false);
}
return _allModelElements;
}
List<ModelElement> _allCanonicalModelElements;
List<ModelElement> get allCanonicalModelElements {
return (_allCanonicalModelElements ??=
allModelElements.where((e) => e.isCanonical).toList());
}
List<Constructor> get constructors {
if (_constructors != null) return _constructors;
_constructors = _cls.constructors.map((e) {
return ModelElement.from(e, library, packageGraph) as Constructor;
}).toList(growable: true)
..sort(byName);
return _constructors;
}
Iterable<Constructor> get publicConstructors =>
model_utils.filterNonPublic(constructors);
/// Returns the library that encloses this element.
@override
ModelElement get enclosingElement => library;
@override
String get fileName => "${name}-class.html";
@override
String get filePath => '${library.dirName}/$fileName';
String get fullkind {
if (isAbstract) return 'abstract $kind';
return kind;
}
bool get hasPublicConstructors => publicConstructors.isNotEmpty;
bool get hasPublicImplementors => publicImplementors.isNotEmpty;
bool get hasInstanceProperties => instanceProperties.isNotEmpty;
bool get hasPublicInterfaces => publicInterfaces.isNotEmpty;
@override
bool get hasPublicMethods =>
publicInstanceMethods.isNotEmpty || publicInheritedMethods.isNotEmpty;
bool get hasPublicMixins => publicMixins.isNotEmpty;
bool get hasModifiers =>
hasPublicMixins ||
hasAnnotations ||
hasPublicInterfaces ||
hasPublicSuperChainReversed ||
hasPublicImplementors ||
hasPotentiallyApplicableExtensions;
@override
bool get hasPublicOperators =>
publicOperators.isNotEmpty || publicInheritedOperators.isNotEmpty;
@override
bool get hasPublicProperties =>
publicInheritedProperties.isNotEmpty ||
publicInstanceProperties.isNotEmpty;
@override
bool get hasPublicStaticMethods => publicStaticMethods.isNotEmpty;
bool get hasPublicSuperChainReversed => publicSuperChainReversed.isNotEmpty;
@override
String get href {
if (!identical(canonicalModelElement, this)) {
return canonicalModelElement?.href;
}
assert(canonicalLibrary != null);
assert(canonicalLibrary == library);
return '${package.baseHref}$filePath';
}
/// Returns all the implementors of this class.
Iterable<Class> get publicImplementors {
return model_utils.filterNonPublic(model_utils.findCanonicalFor(
packageGraph.implementors[href] != null
? packageGraph.implementors[href]
: []));
}
List<Method> get inheritedMethods {
if (_inheritedMethods == null) {
_inheritedMethods = <Method>[];
Set<String> methodNames = methods.map((m) => m.element.name).toSet();
Set<ExecutableElement> inheritedMethodElements =
_inheritedElements.where((e) {
return (e is MethodElement &&
!e.isOperator &&
e is! PropertyAccessorElement &&
!methodNames.contains(e.name));
}).toSet();
for (ExecutableElement e in inheritedMethodElements) {
Method m = ModelElement.from(e, library, packageGraph,
enclosingContainer: this);
_inheritedMethods.add(m);
}
_inheritedMethods.sort(byName);
}
return _inheritedMethods;
}
Iterable get publicInheritedMethods =>
model_utils.filterNonPublic(inheritedMethods);
bool get hasPublicInheritedMethods => publicInheritedMethods.isNotEmpty;
List<Operator> get inheritedOperators {
if (_inheritedOperators == null) {
_inheritedOperators = [];
Set<String> operatorNames = operators.map((o) => o.element.name).toSet();
Set<ExecutableElement> inheritedOperatorElements =
_inheritedElements.where((e) {
return (e is MethodElement &&
e.isOperator &&
!operatorNames.contains(e.name));
}).toSet();
for (ExecutableElement e in inheritedOperatorElements) {
Operator o = ModelElement.from(e, library, packageGraph,
enclosingContainer: this);
_inheritedOperators.add(o);
}
_inheritedOperators.sort(byName);
}
return _inheritedOperators;
}
Iterable<Operator> get publicInheritedOperators =>
model_utils.filterNonPublic(inheritedOperators);
List<Field> get inheritedProperties {
if (_inheritedProperties == null) {
_inheritedProperties = allFields.where((f) => f.isInherited).toList()
..sort(byName);
}
return _inheritedProperties;
}
Iterable<Field> get publicInheritedProperties =>
model_utils.filterNonPublic(inheritedProperties);
Iterable<Method> get publicInstanceMethods => instanceMethods;
List<DefinedElementType> get interfaces => _interfaces;
Iterable<DefinedElementType> get publicInterfaces =>
model_utils.filterNonPublic(interfaces);
bool get isAbstract => _cls.isAbstract;
@override
bool get isCanonical => super.isCanonical && isPublic;
bool get isErrorOrException {
bool _doCheck(ClassElement element) {
return (element.library.isDartCore &&
(element.name == 'Exception' || element.name == 'Error'));
}
// if this class is itself Error or Exception, return true
if (_doCheck(_cls)) return true;
return _cls.allSupertypes.map((t) => t.element).any(_doCheck);
}
/// Returns true if [other] is a parent class for this class.
@override
bool isInheritingFrom(covariant Class other) =>
superChain.map((et) => (et.element as Class)).contains(other);
@override
String get kind => 'class';
Iterable<DefinedElementType> get publicMixins =>
model_utils.filterNonPublic(mixins);
@override
DefinedElementType get modelType => super.modelType;
/// Not the same as superChain as it may include mixins.
/// It's really not even the same as ordinary Dart inheritance, either,
/// because we pretend that interfaces are part of the inheritance chain
/// to include them in the set of things we might link to for documentation
/// purposes in abstract classes.
List<Class> _inheritanceChain;
List<Class> get inheritanceChain {
if (_inheritanceChain == null) {
_inheritanceChain = [];
_inheritanceChain.add(this);
/// Caching should make this recursion a little less painful.
for (Class c in mixins.reversed.map((e) => (e.element as Class))) {
_inheritanceChain.addAll(c.inheritanceChain);
}
for (Class c in superChain.map((e) => (e.element as Class))) {
_inheritanceChain.addAll(c.inheritanceChain);
}
/// Interfaces need to come last, because classes in the superChain might
/// implement them even when they aren't mentioned.
_inheritanceChain.addAll(
interfaces.expand((e) => (e.element as Class).inheritanceChain));
}
return _inheritanceChain.toList(growable: false);
}
List<DefinedElementType> get superChain {
List<DefinedElementType> typeChain = [];
DefinedElementType parent = supertype;
while (parent != null) {
typeChain.add(parent);
if (parent.type is InterfaceType) {
// Avoid adding [Object] to the superChain (_supertype already has this
// check)
if ((parent.type as InterfaceType)?.superclass?.superclass == null) {
parent = null;
} else {
parent = ElementType.from(
(parent.type as InterfaceType).superclass, library, packageGraph);
}
} else {
parent = (parent.element as Class).supertype;
}
}
return typeChain;
}
Iterable<DefinedElementType> get publicSuperChain =>
model_utils.filterNonPublic(superChain);
Iterable<DefinedElementType> get publicSuperChainReversed =>
publicSuperChain.toList().reversed;
List<ExecutableElement> __inheritedElements;
List<ExecutableElement> get _inheritedElements {
if (__inheritedElements == null) {
var classElement = element as ClassElement;
if (classElement.isDartCoreObject) {
return __inheritedElements = <ExecutableElement>[];
}
var inheritance = definingLibrary.inheritanceManager;
var classType = classElement.thisType;
var cmap = inheritance.getInheritedConcreteMap(classType);
var imap = inheritance.getInheritedMap(classType);
var combinedMap = <String, ExecutableElement>{};
for (var nameObj in cmap.keys) {
combinedMap[nameObj.name] = cmap[nameObj];
}
for (var nameObj in imap.keys) {
combinedMap[nameObj.name] ??= imap[nameObj];
}
__inheritedElements = combinedMap.values.toList();
}
return __inheritedElements;
}
List<Field> _fields;
/// Internal only because subclasses are allowed to override how
/// these are mapped to [allInheritedFields] and so forth.
@override
List<Field> get allFields {
if (_fields == null) {
_fields = [];
Set<PropertyAccessorElement> inheritedAccessors = Set()
..addAll(_inheritedElements.whereType<PropertyAccessorElement>());
// This structure keeps track of inherited accessors, allowing lookup
// by field name (stripping the '=' from setters).
Map<String, List<PropertyAccessorElement>> accessorMap = Map();
for (PropertyAccessorElement accessorElement in inheritedAccessors) {
String name = accessorElement.name.replaceFirst('=', '');
accessorMap.putIfAbsent(name, () => []);
accessorMap[name].add(accessorElement);
}
// For half-inherited fields, the analyzer only links the non-inherited
// to the [FieldElement]. Compose our [Field] class by hand by looking up
// inherited accessors that may be related.
for (FieldElement f in _cls.fields) {
PropertyAccessorElement getterElement = f.getter;
if (getterElement == null && accessorMap.containsKey(f.name)) {
getterElement = accessorMap[f.name]
.firstWhere((e) => e.isGetter, orElse: () => null);
}
PropertyAccessorElement setterElement = f.setter;
if (setterElement == null && accessorMap.containsKey(f.name)) {
setterElement = accessorMap[f.name]
.firstWhere((e) => e.isSetter, orElse: () => null);
}
_addSingleField(getterElement, setterElement, inheritedAccessors, f);
accessorMap.remove(f.name);
}
// Now we only have inherited accessors who aren't associated with
// anything in cls._fields.
for (String fieldName in accessorMap.keys) {
List<PropertyAccessorElement> elements =
accessorMap[fieldName].toList();
PropertyAccessorElement getterElement =
elements.firstWhere((e) => e.isGetter, orElse: () => null);
PropertyAccessorElement setterElement =
elements.firstWhere((e) => e.isSetter, orElse: () => null);
_addSingleField(getterElement, setterElement, inheritedAccessors);
}
_fields.sort(byName);
}
return _fields;
}
/// Add a single Field to _fields.
///
/// If [f] is not specified, pick the FieldElement from the PropertyAccessorElement
/// whose enclosing class inherits from the other (defaulting to the getter)
/// and construct a Field using that.
void _addSingleField(
PropertyAccessorElement getterElement,
PropertyAccessorElement setterElement,
Set<PropertyAccessorElement> inheritedAccessors,
[FieldElement f]) {
ContainerAccessor getter =
ContainerAccessor.from(getterElement, inheritedAccessors, this);
ContainerAccessor setter =
ContainerAccessor.from(setterElement, inheritedAccessors, this);
// Rebind getterElement/setterElement as ModelElement.from can resolve
// MultiplyInheritedExecutableElements or resolve Members.
getterElement = getter?.element;
setterElement = setter?.element;
assert(!(getter == null && setter == null));
if (f == null) {
// Pick an appropriate FieldElement to represent this element.
// Only hard when dealing with a synthetic Field.
if (getter != null && setter == null) {
f = getterElement.variable;
} else if (getter == null && setter != null) {
f = setterElement.variable;
} else {
/* getter != null && setter != null */
// In cases where a Field is composed of two Accessors defined in
// different places in the inheritance chain, there are two FieldElements
// for this single Field we're trying to compose. Pick the one closest
// to this class on the inheritance chain.
if ((setter.enclosingElement)
.isInheritingFrom(getter.enclosingElement)) {
f = setterElement.variable;
} else {
f = getterElement.variable;
}
}
}
Field field;
if ((getter == null || getter.isInherited) &&
(setter == null || setter.isInherited)) {
// Field is 100% inherited.
field = ModelElement.from(f, library, packageGraph,
enclosingContainer: this, getter: getter, setter: setter);
} else {
// Field is <100% inherited (could be half-inherited).
// TODO(jcollins-g): Navigation is probably still confusing for
// half-inherited fields when traversing the inheritance tree. Make
// this better, somehow.
field = ModelElement.from(f, library, packageGraph,
getter: getter, setter: setter);
}
_fields.add(field);
}
ClassElement get _cls => (element as ClassElement);
List<Method> _methods;
@override
List<Method> get methods {
if (_methods == null) {
_methods = _cls.methods.map((e) {
return ModelElement.from(e, library, packageGraph) as Method;
}).toList(growable: false)
..sort(byName);
}
return _methods;
}
List<TypeParameter> _typeParameters;
// a stronger hash?
@override
List<TypeParameter> get typeParameters {
if (_typeParameters == null) {
_typeParameters = _cls.typeParameters.map((f) {
var lib = Library(f.enclosingElement.library, packageGraph);
return ModelElement.from(f, lib, packageGraph) as TypeParameter;
}).toList();
}
return _typeParameters;
}
@override
bool operator ==(o) =>
o is Class &&
name == o.name &&
o.library.name == library.name &&
o.library.package.name == library.package.name;
}