blob: 49d804980ed6b6b0c2e1474036808570f44e3598 [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/extension_target.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/model_utils.dart' as model_utils;
import 'package:dartdoc/src/quiver.dart' as quiver;
import 'package:meta/meta.dart';
/// A [Container] defined with a `class` declaration in Dart.
///
/// Members follow similar naming rules to [Container], with the following
/// additions:
///
/// **instance**: As with [Container], but also includes inherited children.
/// **inherited**: Filtered getters giving only inherited children.
class Class extends Container
with Categorization, ExtensionTarget
implements EnclosedElement {
// TODO(srawlins): To make final, remove public getter, setter, rename to be
// public, and add `final` modifier.
List<DefinedElementType> _mixins;
List<DefinedElementType> get mixins => _mixins;
@Deprecated('Field intended to be final; setter will be removed as early as '
'Dartdoc 1.0.0')
set mixins(List<DefinedElementType> value) => _mixins = value;
// TODO(srawlins): To make final, remove public getter, setter, rename to be
// public, and add `final` modifier.
DefinedElementType _supertype;
DefinedElementType get supertype => _supertype;
@Deprecated('Field intended to be final; setter will be removed as early as '
'Dartdoc 1.0.0')
set supertype(DefinedElementType value) => _supertype = value;
final List<DefinedElementType> _interfaces;
Class(ClassElement element, Library library, PackageGraph packageGraph)
: _mixins = element.mixins
.map<DefinedElementType>(
(f) => ElementType.from(f, library, packageGraph))
.where((mixin) => mixin != null)
.toList(growable: false),
_supertype = element.supertype?.element?.supertype == null
? null
: ElementType.from(element.supertype, library, packageGraph),
_interfaces = element.interfaces
.map<DefinedElementType>(
(f) => ElementType.from(f, library, packageGraph))
.toList(growable: false),
super(element, library, packageGraph) {
packageGraph.specialClasses.addSpecial(this);
}
Constructor _unnamedConstructor;
Constructor get unnamedConstructor {
_unnamedConstructor ??= constructors
.firstWhere((c) => c.isUnnamedConstructor, orElse: () => null);
return _unnamedConstructor;
}
@Deprecated(
'Renamed to `unnamedConstructor`; this getter with the old name will be '
'removed as early as Dartdoc 1.0.0')
Constructor get defaultConstructor => unnamedConstructor;
@override
Iterable<Method> get instanceMethods =>
quiver.concat([super.instanceMethods, inheritedMethods]);
@override
bool get publicInheritedInstanceMethods =>
instanceMethods.every((f) => f.isInherited);
@override
Iterable<Operator> get instanceOperators =>
quiver.concat([super.instanceOperators, inheritedOperators]);
List<ModelElement> _allModelElements;
@override
List<ModelElement> get allModelElements {
_allModelElements ??= List.from(
quiver.concat<ModelElement>([
super.allModelElements,
constructors,
typeParameters,
]),
growable: false);
return _allModelElements;
}
List<ModelElement> _allCanonicalModelElements;
List<ModelElement> get allCanonicalModelElements {
return (_allCanonicalModelElements ??=
allModelElements.where((e) => e.isCanonical).toList());
}
Iterable<Constructor> get constructors => element.constructors
.map((e) => ModelElement.from(e, library, packageGraph) as Constructor);
@visibleForTesting
Iterable<Constructor> get publicConstructors =>
model_utils.filterNonPublic(constructors);
/// Returns the library that encloses this element.
@override
ModelElement get enclosingElement => library;
@override
ClassElement get element => super.element;
@override
String get fileName => '$name-class.$fileType';
@override
String get filePath => '${library.dirName}/$fileName';
String get fullkind {
if (isAbstract) return 'abstract $kind';
return kind;
}
@override
bool get hasPublicConstructors => publicConstructorsSorted.isNotEmpty;
List<Constructor> _publicConstructorsSorted;
@override
List<Constructor> get publicConstructorsSorted =>
_publicConstructorsSorted ??= publicConstructors.toList()..sort(byName);
bool get hasPublicImplementors => publicImplementors.isNotEmpty;
bool get hasPublicInterfaces => publicInterfaces.isNotEmpty;
bool get hasPublicMixins => publicMixins.isNotEmpty;
@override
bool get hasModifiers =>
hasPublicMixins ||
hasAnnotations ||
hasPublicInterfaces ||
hasPublicSuperChainReversed ||
hasPublicImplementors ||
hasPotentiallyApplicableExtensions;
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 the [Class] with the library in which [element] is defined.
Class get definingClass =>
ModelElement.from(element, definingLibrary, packageGraph);
/// Returns all the "immediate" public implementors of this class.
///
/// If this class has a private implementor, then that is counted as a proxy
/// for any public implementors of that private class.
Iterable<Class> get publicImplementors {
var result = <Class>{};
var seen = <Class>{};
// Recursively adds [implementor] if public, or the implementors of
// [implementor] if not.
void addToResult(Class implementor) {
if (seen.contains(implementor)) return;
seen.add(implementor);
if (implementor.isPublicAndPackageDocumented) {
result.add(implementor);
} else {
model_utils
.findCanonicalFor(packageGraph.implementors[implementor] ?? [])
.forEach(addToResult);
}
}
model_utils
.findCanonicalFor(packageGraph.implementors[this] ?? [])
.forEach(addToResult);
return result;
}
/*lazy final*/ List<Method> _inheritedMethods;
Iterable<Method> get inheritedMethods {
if (_inheritedMethods == null) {
_inheritedMethods = <Method>[];
var methodNames = declaredMethods.map((m) => m.element.name).toSet();
var inheritedMethodElements = _inheritedElements.where((e) {
return (e is MethodElement &&
!e.isOperator &&
e is! PropertyAccessorElement &&
!methodNames.contains(e.name));
}).toSet();
for (var e in inheritedMethodElements) {
Method m = ModelElement.from(e, library, packageGraph,
enclosingContainer: this);
_inheritedMethods.add(m);
}
}
return _inheritedMethods;
}
Iterable<Method> get publicInheritedMethods =>
model_utils.filterNonPublic(inheritedMethods);
bool get hasPublicInheritedMethods => publicInheritedMethods.isNotEmpty;
/*lazy final*/ List<Operator> _inheritedOperators;
Iterable<Operator> get inheritedOperators {
if (_inheritedOperators == null) {
_inheritedOperators = [];
var operatorNames = declaredOperators.map((o) => o.element.name).toSet();
var inheritedOperatorElements = _inheritedElements.where((e) {
return (e is MethodElement &&
e.isOperator &&
!operatorNames.contains(e.name));
}).toSet();
for (var e in inheritedOperatorElements) {
Operator o = ModelElement.from(e, library, packageGraph,
enclosingContainer: this);
_inheritedOperators.add(o);
}
}
return _inheritedOperators;
}
@override
Iterable<Operator> get publicInheritedInstanceOperators =>
model_utils.filterNonPublic(inheritedOperators);
Iterable<Field> get inheritedFields => allFields.where((f) => f.isInherited);
Iterable<Field> get publicInheritedFields =>
model_utils.filterNonPublic(inheritedFields);
List<DefinedElementType> get interfaces => _interfaces;
Iterable<DefinedElementType> get publicInterfaces =>
model_utils.filterNonPublic(interfaces);
bool get isAbstract => element.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(element)) return true;
return element.allSupertypes.map((t) => t.element).any(_doCheck);
}
/// Returns true if [other] is a parent class for this class.
bool _isInheritingFrom(Class other) =>
superChain.map((et) => (et.element as Class)).contains(other);
@Deprecated(
'Public method intended to be private; will be removed as early as '
'Dartdoc 1.0.0')
bool isInheritingFrom(Class other) => _isInheritingFrom(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 (var c in mixins.reversed.map((e) => (e.element as Class))) {
_inheritanceChain.addAll(c.inheritanceChain);
}
for (var 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 {
var typeChain = <DefinedElementType>[];
var 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) {
if (element.isDartCoreObject) {
return __inheritedElements = <ExecutableElement>[];
}
if (definingLibrary == null) {
// [definingLibrary] may be null if [element] has been imported or
// exported with a non-normalized URI, like "src//a.dart".
// TODO(srawlins): It would be nice to allow references from such
// libraries, but for now, PackageGraph.allLibraries is a Map with
// LibraryElement keys, which include [Element.location] in their
// `==` calculation; I think we should not key off of Elements.
return __inheritedElements = <ExecutableElement>[];
}
var inheritance = definingLibrary.inheritanceManager;
var cmap = inheritance.getInheritedConcreteMap2(element);
var imap = inheritance.getInheritedMap2(element);
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> _allFields;
List<Field> get allFields {
if (_allFields == null) {
_allFields = [];
var inheritedAccessorElements = <PropertyAccessorElement>{}
..addAll(_inheritedElements.whereType<PropertyAccessorElement>());
// This structure keeps track of inherited accessors, allowing lookup
// by field name (stripping the '=' from setters).
var accessorMap = <String, List<PropertyAccessorElement>>{};
for (var accessorElement in inheritedAccessorElements) {
var name = accessorElement.name.replaceFirst('=', '');
accessorMap.putIfAbsent(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 (var f in element.fields) {
var getterElement = f.getter;
if (getterElement == null && accessorMap.containsKey(f.name)) {
getterElement = accessorMap[f.name]
.firstWhere((e) => e.isGetter, orElse: () => null);
}
var setterElement = f.setter;
if (setterElement == null && accessorMap.containsKey(f.name)) {
setterElement = accessorMap[f.name]
.firstWhere((e) => e.isSetter, orElse: () => null);
}
_addSingleField(
getterElement, setterElement, inheritedAccessorElements, f);
accessorMap.remove(f.name);
}
// Now we only have inherited accessors who aren't associated with
// anything in cls._fields.
for (var fieldName in accessorMap.keys) {
var elements = accessorMap[fieldName].toList();
var getterElement =
elements.firstWhere((e) => e.isGetter, orElse: () => null);
var setterElement =
elements.firstWhere((e) => e.isSetter, orElse: () => null);
_addSingleField(
getterElement, setterElement, inheritedAccessorElements);
}
}
return _allFields;
}
@override
Iterable<Field> get declaredFields => allFields.where((f) => !f.isInherited);
/// 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]) {
var getter =
ContainerAccessor.from(getterElement, inheritedAccessors, this);
var 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 is Class &&
(setter.enclosingElement as Class)
._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.fromPropertyInducingElement(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.fromPropertyInducingElement(f, library, packageGraph,
getter: getter, setter: setter);
}
_allFields.add(field);
}
Iterable<Method> _declaredMethods;
@override
Iterable<Method> get declaredMethods =>
_declaredMethods ??= element.methods.map((e) {
return ModelElement.from(e, library, packageGraph) as Method;
});
List<TypeParameter> _typeParameters;
// a stronger hash?
@override
List<TypeParameter> get typeParameters {
_typeParameters ??= element.typeParameters.map((f) {
var lib = Library(f.enclosingElement.library, packageGraph);
return ModelElement.from(f, lib, packageGraph) as TypeParameter;
}).toList();
return _typeParameters;
}
Iterable<Field> _instanceFields;
@override
Iterable<Field> get instanceFields =>
_instanceFields ??= allFields.where((f) => !f.isStatic);
@override
bool get publicInheritedInstanceFields =>
publicInstanceFields.every((f) => f.isInherited);
@override
Iterable<Field> get constantFields => allFields.where((f) => f.isConst);
@override
bool operator ==(Object o) =>
o is Class &&
name == o.name &&
o.library.name == library.name &&
o.library.package.name == library.package.name;
}