blob: 109d92b84da9e442140a2739e48b8cf635dbc7bf [file] [log] [blame]
// Copyright (c) 2021, 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/element2.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:dartdoc/src/element_type.dart';
import 'package:dartdoc/src/model/comment_referable.dart';
import 'package:dartdoc/src/model/container_modifiers.dart';
import 'package:dartdoc/src/model/language_feature.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/model_utils.dart' as model_utils;
import 'package:meta/meta.dart';
/// A mixin to build an [InheritingContainer] capable of being constructed
/// with a direct call to a [Constructor] in Dart.
///
/// Note that [Constructor]s are not considered to be modifiers so a
/// [hasModifiers] override is not necessary for this mixin.
mixin Constructable implements InheritingContainer {
late final List<Constructor> constructors = element.constructors2
.map((e) => getModelFor(e, library) as Constructor)
.toList(growable: false);
@override
late final List<Constructor> publicConstructorsSorted =
constructors.wherePublic.toList(growable: false)..sort(byName);
@override
@visibleForOverriding
Map<String, CommentReferable> get extraReferenceChildren => {
for (var container in superChain.wherePublic
.map((t) => t.modelElement)
.whereType<Container>()) ...{
if (container is Constructable)
..._mapConstructorsByName(container.constructors),
for (var modelElement in [
// TODO(jcollins-g): wean important users off of relying on static
// method inheritance (dart-lang/dartdoc#2698).
...container.staticFields, ...container.staticMethods
])
modelElement.referenceName: modelElement,
},
..._mapConstructorsByName(constructors),
};
@override
bool get hasPublicConstructors => publicConstructorsSorted.isNotEmpty;
static Map<String, CommentReferable> _mapConstructorsByName(
Iterable<Constructor> constructors) =>
{
for (var constructor in constructors) ...{
constructor.referenceName: constructor,
'${constructor.enclosingElement.referenceName}.${constructor.referenceName}':
constructor,
if (constructor.isUnnamedConstructor) 'new': constructor,
},
};
}
/// A [Container] that participates in inheritance 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.
abstract class InheritingContainer extends Container {
InheritingContainer(super.library, super.packageGraph);
DefinedElementType? get supertype {
final elementSupertype = element.supertype;
return elementSupertype == null ||
elementSupertype.element3.supertype == null
? null
: getTypeFor(elementSupertype, library) as DefinedElementType;
}
/// Class modifiers from the Dart feature specification.
///
/// These apply to or have some meaning for [Class]es and [Mixin]s.
late final List<ContainerModifier> containerModifiers = [
if (isAbstract) ContainerModifier.abstract,
if (isSealed) ContainerModifier.sealed,
if (isBase) ContainerModifier.base,
if (isImplementableInterface) ContainerModifier.interface,
if (isFinal) ContainerModifier.finalModifier,
if (isMixinClass) ContainerModifier.mixin,
]..sort();
@override
late final List<LanguageFeature> displayedLanguageFeatures =
containerModifiers.asLanguageFeatureSet.toList();
late final List<ModelElement> _allModelElements = [
...super.allModelElements,
...typeParameters,
];
Iterable<Method> get inheritedMethods {
var methodNames = declaredMethods.map((m) => m.element.name3).toSet();
var inheritedMethodElements = _inheritedElements
.whereType<MethodElement2>()
.where((e) =>
!e.isOperator &&
e is! PropertyAccessorElement2 &&
!methodNames.contains(e.name3))
.toSet();
return [
for (var e in inheritedMethodElements)
getModelFor(e, library, enclosingContainer: this) as Method,
];
}
List<Operator> get inheritedOperators {
var operatorNames =
declaredOperators.map((o) => o.element.lookupName).toSet();
var inheritedOperatorElements = _inheritedElements
.whereType<MethodElement2>()
.where((e) => e.isOperator && !operatorNames.contains(e.lookupName))
.toSet();
return [
for (var e in inheritedOperatorElements)
getModelFor(e, library, enclosingContainer: this) as Operator,
];
}
late final DefinedElementType modelType =
getTypeFor(element.thisType, library) as DefinedElementType;
/// A list of the inherited executable elements, one element per inherited
/// `Name`.
///
/// In this list, elements that are "closer" in the inheritance chain to
/// _this_ element are preferred over elements that are further away. In the
/// case of ties, concrete inherited elements are prefered to non-concrete
/// ones.
late final List<ExecutableElement2> _inheritedElements = () {
if (element case ClassElement2 classElement
when classElement.isDartCoreObject) {
return const <ExecutableElement2>[];
}
// The mapping of all of the inherited element names to their _concrete_
// implementation element.
var concreteInheritanceMap =
packageGraph.inheritanceManager.getInheritedConcreteMap(element);
// The mapping of all inherited element names to the nearest inherited
// element that they resolve to.
var inheritanceMap =
packageGraph.inheritanceManager.getInheritedMap(element);
var inheritanceChainElements =
inheritanceChain.map((c) => c.element).toList(growable: false);
// A combined map of names to inherited _concrete_ Elements, and other
// inherited Elements.
var combinedMap = {
for (var MapEntry(:key, :value) in concreteInheritanceMap.entries)
key.name: value,
};
for (var MapEntry(key: name, value: inheritedElement)
in inheritanceMap.entries) {
var combinedMapElement = combinedMap[name.name];
if (combinedMapElement == null) {
combinedMap[name.name] = inheritedElement;
continue;
}
// Elements in the inheritance chain starting from `this.element` up to,
// but not including, `Object`.
var enclosingElement =
inheritedElement.enclosingElement2 as InterfaceElement2;
assert(inheritanceChainElements.contains(enclosingElement) ||
enclosingElement.isDartCoreObject);
// If the concrete element from `getInheritedConcreteMap2` is farther in
// the inheritance chain from this class than the (non-concrete) one
// provided by `getInheritedMap2`, prefer the latter. This correctly
// accounts for intermediate abstract classes that have method/field
// implementations.
var enclosingElementFromCombined =
combinedMapElement.enclosingElement2 as InterfaceElement2;
if (inheritanceChainElements.indexOf(enclosingElementFromCombined) <
inheritanceChainElements.indexOf(enclosingElement)) {
combinedMap[name.name] = inheritedElement;
}
}
// Finally, return all of the elements ultimately collected in the combined
// map.
return combinedMap.values.toList(growable: false);
}();
/// All fields defined on this container, _including inherited fields_.
late List<Field> allFields = () {
var inheritedAccessorElements = {
..._inheritedElements.whereType<PropertyAccessorElement2>()
};
// This structure keeps track of inherited accessors, allowing lookup
// by field name (stripping the '=' from setters).
// TODO(srawlins): Each value List should only contain 1 or 2 elements:
// up to one getter and one setter. We then perform repeated
// `.firstWhereOrNull((e) => e.isGetter)` and
// `.firstWhereOrNull((e) => e.isSetter)` calls, which would be much simpler
// if we used some sort of "pair" class instead.
var accessorMap = <String, List<PropertyAccessorElement2>>{};
for (var accessorElement in inheritedAccessorElements) {
accessorMap
.putIfAbsent(
accessorElement.name3?.replaceFirst('=', '') ?? '', () => [])
.add(accessorElement);
}
var fields = <Field>[];
// 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 field in element.fields2) {
var getterElement = field.getter2;
if (getterElement == null && accessorMap.containsKey(field.name3)) {
getterElement = accessorMap[field.name3]!
.firstWhereOrNull((e) => e is GetterElement) as GetterElement?;
}
var setterElement = field.setter2;
if (setterElement == null && accessorMap.containsKey(field.name3)) {
setterElement = accessorMap[field.name3]!
.firstWhereOrNull((e) => e is SetterElement) as SetterElement?;
}
fields.add(_createSingleField(
getterElement, setterElement, inheritedAccessorElements, field));
accessorMap.remove(field.name3);
}
// Now we only have inherited accessors who aren't associated with
// anything in the fields.
accessorMap.forEach((fieldName, elements) {
final getterElement =
elements.firstWhereOrNull((e) => e is GetterElement);
final setterElement =
elements.firstWhereOrNull((e) => e is SetterElement);
fields.add(_createSingleField(
getterElement, setterElement, inheritedAccessorElements));
});
return fields;
}();
@override
late final List<Method> declaredMethods = element.methods2
.map((e) => getModelFor(e, library) as Method)
.toList(growable: false);
@override
late final List<TypeParameter> typeParameters = element.typeParameters2
.map((typeParameter) => getModelFor(
typeParameter,
getModelForElement(typeParameter.enclosingElement2!.library2!)
as Library) as TypeParameter)
.toList(growable: false);
bool get hasPotentiallyApplicableExtensions =>
potentiallyApplicableExtensionsSorted.isNotEmpty;
/// The sorted list of potentially applicable extensions, for display in
/// templates.
///
/// This is defined as those extensions where an instantiation of the type
/// defined by [element] can exist where this extension applies, not including
/// any extension that applies to every type.
late final List<Extension> potentiallyApplicableExtensionsSorted =
packageGraph.extensions
.where((e) => !e.alwaysApplies)
.where((e) => e.couldApplyTo(this))
.toList(growable: false)
..sort(byName);
@override
List<ModelElement> get allModelElements => _allModelElements;
@override
Iterable<Field> get constantFields => allFields.where((f) => f.isConst);
@override
Iterable<Field> get declaredFields => allFields.where((f) => !f.isInherited);
/// The [InheritingContainer] with the library in which [element] is defined.
InheritingContainer get definingContainer =>
getModelFor(element, library) as InheritingContainer;
@override
@override
InterfaceElement2 get element;
@override
Library get enclosingElement => library;
String get fullkind => kind.toString();
/// Whether this container has any "modifiers" that should be displayed on the
/// container's page, above it's members.
// TODO(srawlins): This name is confusing, especially after the language
// feature, "new class modifiers." I think the only purpose is to avoid
// writing a `<section>` and a `<dl>` tag, if they would be empty. But with
// CSS, we should be able to just always write those tags, and visually make
// them take up zero space if they are empty.
bool get hasModifiers =>
hasAnnotations ||
hasPublicSuperChainReversed ||
hasPotentiallyApplicableExtensions ||
hasPublicInterfaces ||
hasPublicImplementers;
@visibleForTesting
bool get hasPublicInheritedMethods => inheritedMethods.any((e) => e.isPublic);
bool get hasPublicSuperChainReversed => superChain.any((e) => e.isPublic);
/// A sorted list of [element]'s inheritance chain, including interfaces and
/// mixins.
///
/// Note: this list is really not even the same as ordinary Dart inheritance,
/// 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.
List<InheritingContainer> get inheritanceChain;
@visibleForTesting
Iterable<Field> get inheritedFields => allFields.where((f) => f.isInherited);
@override
Iterable<Field> get instanceFields => allFields.where((f) => !f.isStatic);
@override
late final List<Field> availableInstanceFieldsSorted = [
...instanceFields.wherePublic,
..._extensionInstanceFields.wherePublic,
]..sort();
List<Field> get _extensionInstanceFields => [
for (var extension in potentiallyApplicableExtensionsSorted)
for (var field in extension.instanceFields)
getModelForPropertyInducingElement(
field.element,
library,
enclosingContainer: extension,
getter: field.getter,
setter: field.setter,
) as Field,
];
@override
Iterable<Method> get instanceMethods => [
...declaredMethods.where((m) => !m.isStatic && !m.isOperator),
...inheritedMethods,
];
@override
late final List<Method> availableInstanceMethodsSorted = [
...instanceMethods.wherePublic,
..._extensionInstanceMethods.wherePublic,
]..sort();
List<Method> get _extensionInstanceMethods => [
for (var extension in potentiallyApplicableExtensionsSorted)
for (var method in extension.instanceMethods)
getModelFor(method.element, library,
enclosingContainer: extension) as Method,
];
@override
Iterable<Operator> get instanceOperators =>
[...super.instanceOperators, ...inheritedOperators];
@override
late final List<Operator> availableInstanceOperatorsSorted = [
...instanceOperators.wherePublic,
..._extensionInstanceOperators.wherePublic,
]..sort();
List<Operator> get _extensionInstanceOperators => [
for (var extension in potentiallyApplicableExtensionsSorted)
for (var operator in extension.instanceOperators)
getModelFor(operator.element, library,
enclosingContainer: extension) as Operator,
];
bool get isAbstract;
bool get isBase;
@override
bool get isCanonical => super.isCanonical && isPublic;
@override
bool get isFinal;
/// Whether this element is a publicly implementable interface.
bool get isImplementableInterface;
bool get isMixinClass;
bool get isSealed;
@override
// TODO(srawlins): Rename this, and `publicInheritedInstanceMethods` and
// `publicInheritedInstanceOperators` after custom template support is
// removed. Maybe `areAllInstanceFieldsInherited`.
bool get publicInheritedInstanceFields =>
instanceFields.wherePublic.every((f) => f.isInherited);
@override
bool get publicInheritedInstanceMethods =>
instanceMethods.every((f) => f.isInherited);
@override
bool get publicInheritedInstanceOperators =>
instanceOperators.wherePublic.every((f) => f.isInherited);
@visibleForTesting
late final List<DefinedElementType> directInterfaces = [
for (var interface in element.interfaces)
getTypeFor(interface, library) as DefinedElementType
];
bool get hasPublicImplementers => publicImplementersSorted.isNotEmpty;
/// All the "immediate" public implementers of this container.
///
/// For a [Mixin], this is actually the mixin applications that use the
/// [Mixin].
///
/// If this container has a private implementer, then that is counted as a
/// proxy for any public implementers of that private container.
List<InheritingContainer> get publicImplementersSorted {
var result = <InheritingContainer>{};
var seen = <InheritingContainer>{};
// Recursively adds [implementer] if public, or just the implementers of
// [implementer] if not.
void addToResult(InheritingContainer implementer) {
if (seen.contains(implementer)) return;
seen.add(implementer);
if (implementer.isPublicAndPackageDocumented) {
result.add(implementer);
} else {
var implementers = packageGraph.implementers[implementer];
if (implementers != null) {
_findCanonicalFor(implementers).forEach(addToResult);
}
}
}
var immediateImplementers = packageGraph.implementers[this];
if (immediateImplementers != null) {
_findCanonicalFor(immediateImplementers).forEach(addToResult);
}
return result.toList(growable: false)..sort(byName);
}
/// Finds canonical classes for all classes in the iterable, if possible.
/// If a canonical class can not be found, returns the original class.
Iterable<InheritingContainer> _findCanonicalFor(
Iterable<InheritingContainer> containers) {
return containers.map((container) {
var canonical = packageGraph.findCanonicalModelElementFor(container);
return canonical as InheritingContainer? ?? container;
});
}
bool get hasPublicInterfaces => publicInterfaces.isNotEmpty;
List<InheritingContainer> get interfaceElements => [
for (var interface in directInterfaces)
interface.modelElement as InheritingContainer,
];
/// The public interfaces of this container.
///
/// This list may include substitutions for intermediate private interfaces,
/// and so unlike other `public*` methods, is not a strict subset of
/// [directInterfaces] (the direct interfaces).
List<DefinedElementType> get publicInterfaces {
var interfaceElements = <InterfaceElement2>{};
var interfaces = <DefinedElementType>[];
// Only interfaces with unique elements should be returned. Elements can
// implement an interface through multiple inheritance routes (e.g.
// `List<E>` implements `Iterable<E>` but also `_ListIterable<E>` which
// implements `EfficientLengthIterable<T>` which implements `Iterable<T>`),
// but there is no chance of type arguments differing, as that is illegal.
void addInterfaceIfUnique(DefinedElementType type) {
var firstPublicSuperElement = type.modelElement.element;
if (firstPublicSuperElement is InterfaceElement2) {
if (interfaceElements.add(firstPublicSuperElement)) {
interfaces.add(type);
}
}
}
void addFromSupertype(DefinedElementType supertype,
{required bool addSupertypes}) {
var superElement = supertype.modelElement;
/// Do not recurse if we can find an element here.
if (superElement.canonicalModelElement != null) {
if (addSupertypes) addInterfaceIfUnique(supertype);
return;
}
// This type is not backed by a canonical Class; it is not documented.
// Search it's `superChain` and `publicInterfaces` to pretend that `this`
// container directly implements canonical classes further up the chain.
if (superElement is! InheritingContainer) {
assert(
false,
'Cannot handle intermediate non-public interfaces created by '
"ModelElements that are not classes or mixins: '$fullyQualifiedName' "
"contains a supertype '$supertype', defined by '$superElement'",
);
return;
}
var publicSuperChain = superElement.superChain.wherePublic;
if (publicSuperChain.isNotEmpty && addSupertypes) {
addInterfaceIfUnique(publicSuperChain.first);
}
superElement.publicInterfaces.forEach(addInterfaceIfUnique);
}
for (var interface in directInterfaces) {
addFromSupertype(interface, addSupertypes: true);
}
for (var supertype in superChain) {
var interfaceElement = supertype.modelElement;
// Do not recurse if we can find an element here.
if (interfaceElement.canonicalModelElement != null) {
continue;
}
addFromSupertype(supertype, addSupertypes: false);
}
if (this case Class(:var mixedInTypes) || Enum(:var mixedInTypes)) {
for (var mixin in mixedInTypes) {
addFromSupertype(mixin, addSupertypes: false);
}
}
return interfaces;
}
Iterable<InheritingContainer> get publicInterfaceElements => [
for (var interface in publicInterfaces)
interface.modelElement as InheritingContainer,
];
Iterable<DefinedElementType> get publicSuperChainReversed =>
[...superChain.wherePublic].reversed;
/// The chain of super-types, starting with [supertype], up to, but not
/// including, `Object`.
late final List<DefinedElementType> superChain = () {
var typeChain = <DefinedElementType>[];
var parent = supertype;
while (parent != null) {
typeChain.add(parent);
final parentType = parent.type;
if (parentType is! InterfaceType) {
throw StateError("ancestor of '$this' is '$parent' with model element "
"'${parent.modelElement}'");
}
var superclass = parentType.superclass;
// Avoid adding `Object` to the `superChain` (`_supertype` already has
// this check).
if (superclass == null || superclass.superclass == null) {
break;
}
parent = getTypeFor(superclass, library) as DefinedElementType?;
}
return typeChain;
}();
/// Creates a single Field.
///
/// If [field] is not specified, picks the [FieldElement2] from the
/// [PropertyAccessorElement2] whose enclosing class inherits from the other
/// (defaulting to the getter) and constructs a [Field] using that.
Field _createSingleField(
PropertyAccessorElement2? getterElement,
PropertyAccessorElement2? setterElement,
Set<PropertyAccessorElement2> inheritedAccessors,
[FieldElement2? field]) {
// Return a [ContainerAccessor] with `isInherited = true` if [element] is
// in [inheritedAccessors].
ContainerAccessor? containerAccessorFrom(
PropertyAccessorElement2? element) {
if (element == null) return null;
final enclosingContainer =
inheritedAccessors.contains(element) ? this : null;
return getModelFor(element, library,
enclosingContainer: enclosingContainer) as ContainerAccessor;
}
var getter = containerAccessorFrom(getterElement);
var setter = containerAccessorFrom(setterElement);
// Rebind [getterElement], [setterElement] as [ModelElement.from] can
// resolve [Member]s.
getterElement = getter?.element;
setterElement = setter?.element;
assert(getter != null || setter != null);
if (field == null) {
// Pick an appropriate [FieldElement] to represent this element.
// Only hard when dealing with a synthetic [Field].
if (getter != null && setter == null) {
field = getterElement!.variable3 as FieldElement2;
} else if (getter == null && setter != null) {
field = setterElement!.variable3 as FieldElement2;
} else {
// In this case: `getter != null && setter != null`.
getter!;
setter!;
final setterEnclosingElement = setter.enclosingElement;
// In cases where a Field is composed of two Accessors defined in
// different places in the inheritance chain, there are two
// [FieldElement]s for this single [Field] we're trying to compose.
// Pick the one closest to this class on the inheritance chain.
if (setterEnclosingElement is Class &&
setterEnclosingElement._isInheritingFrom(
getter.enclosingElement as InheritingContainer)) {
field = setterElement!.variable3 as FieldElement2;
} else {
field = getterElement!.variable3 as FieldElement2;
}
}
}
if ((getter == null || getter.isInherited) &&
(setter == null || setter.isInherited)) {
// Field is 100% inherited.
return getModelForPropertyInducingElement(field, library,
getter: getter, setter: setter, enclosingContainer: this) as Field;
} 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.
return getModelForPropertyInducingElement(field, library,
getter: getter, setter: setter) as Field;
}
}
/// Returns true if [other] is a parent class for this class.
bool _isInheritingFrom(InheritingContainer other) => superChain
.map((et) => et.modelElement as InheritingContainer)
.contains(other);
}
/// Add the ability to support mixed-in types to an [InheritingContainer].
mixin MixedInTypes on InheritingContainer {
late final List<DefinedElementType> mixedInTypes = element.mixins
.map((f) => getTypeFor(f, library) as DefinedElementType)
.toList(growable: false);
@override
bool get hasModifiers => super.hasModifiers || hasPublicMixedInTypes;
bool get hasPublicMixedInTypes => mixedInTypes.any((e) => e.isPublic);
Iterable<DefinedElementType> get publicMixedInTypes =>
mixedInTypes.wherePublic;
}
extension on InterfaceElement2 {
bool get isDartCoreObject =>
name3 == 'Object' && library2.name3 == 'dart.core';
}
extension DefinedElementTypeIterableExtension on Iterable<DefinedElementType> {
/// The [ModelElement] for each element.
List<InheritingContainer> get modelElements =>
map((e) => e.modelElement as InheritingContainer).toList();
}
extension InheritingContainerIterableExtension
on Iterable<InheritingContainer> {
/// Expands each element to its inheritance chain.
Iterable<InheritingContainer> get expandInheritanceChain =>
expand((e) => e.inheritanceChain);
}