blob: 840d9416abd0132f3ea3e09fc767386572d98405 [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/element.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/extension_target.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 on InheritingContainer {
late final Iterable<Constructor> constructors = element.constructors
.map((e) => modelBuilder.from(e, library) as Constructor)
.toList(growable: false);
@override
late final List<Constructor> publicConstructorsSorted =
model_utils.filterNonPublic(constructors).toList(growable: false)..sort();
@override
@visibleForOverriding
Iterable<MapEntry<String, CommentReferable>>
get extraReferenceChildren sync* {
yield* _constructorGenerator(constructors);
// TODO(jcollins-g): wean important users off of relying on static method
// inheritance (dart-lang/dartdoc#2698)
for (var container
in publicSuperChain.map((t) => t.modelElement).whereType<Container>()) {
for (var modelElement in [
...container.staticFields,
...container.staticMethods,
]) {
yield MapEntry(modelElement.referenceName, modelElement);
}
if (container is Constructable) {
yield* _constructorGenerator(container.constructors);
}
}
}
@override
bool get hasPublicConstructors => publicConstructorsSorted.isNotEmpty;
static Iterable<MapEntry<String, CommentReferable>> _constructorGenerator(
Iterable<Constructor> source) sync* {
for (var constructor in source) {
yield MapEntry(constructor.referenceName, constructor);
yield MapEntry(
'${constructor.enclosingElement.referenceName}.${constructor.referenceName}',
constructor);
if (constructor.isDefaultConstructor) {
yield MapEntry('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
with ExtensionTarget
implements EnclosedElement {
late final DefinedElementType? supertype = () {
final elementSupertype = element.supertype;
return elementSupertype == null ||
elementSupertype.element.supertype == null
? null
: modelBuilder.typeFrom(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 (isInterface) ContainerModifier.interface,
if (isFinal) ContainerModifier.finalModifier,
if (isMixinClass) ContainerModifier.mixin,
]..sort();
@override
late final List<LanguageFeature> displayedLanguageFeatures =
containerModifiers
.asLanguageFeatureSet(
packageGraph.rendererFactory.languageFeatureRenderer)
.toList();
late final List<ModelElement> _allModelElements = [
...super.allModelElements,
...typeParameters,
];
late final Iterable<Method> inheritedMethods = () {
var methodNames = declaredMethods.map((m) => m.element.name).toSet();
var inheritedMethodElements = _inheritedElements
.whereType<MethodElement>()
.where((e) =>
!e.isOperator &&
e is! PropertyAccessorElement &&
!methodNames.contains(e.name))
.toSet();
return [
for (var e in inheritedMethodElements)
modelBuilder.from(e, library, enclosingContainer: this) as Method,
];
}();
late final List<Operator> inheritedOperators = () {
var operatorNames = declaredOperators.map((o) => o.element.name).toSet();
var inheritedOperatorElements = _inheritedElements
.whereType<MethodElement>()
.where((e) => e.isOperator && !operatorNames.contains(e.name))
.toSet();
return [
for (var e in inheritedOperatorElements)
modelBuilder.from(e, library, enclosingContainer: this) as Operator,
];
}();
@override
late final DefinedElementType modelType =
modelBuilder.typeFrom(element.thisType, library) as DefinedElementType;
late final List<DefinedElementType> publicSuperChain =
model_utils.filterNonPublic(superChain).toList(growable: false);
late final List<ExecutableElement> _inheritedElements = () {
if (element is ClassElement && (element as ClassElement).isDartCoreObject) {
return const <ExecutableElement>[];
}
final inheritance = definingLibrary.inheritanceManager;
final concreteInheritenceMap =
inheritance.getInheritedConcreteMap2(element);
final inheritenceMap = inheritance.getInheritedMap2(element);
List<InterfaceElement>? inheritanceChainElements;
final combinedMap = {
for (final name in concreteInheritenceMap.keys)
name.name: concreteInheritenceMap[name]!,
};
for (final name in inheritenceMap.keys) {
final inheritenceElement = inheritenceMap[name]!;
final combinedMapElement = combinedMap[name.name];
if (combinedMapElement == null) {
combinedMap[name.name] = inheritenceElement;
continue;
}
// Elements in the inheritance chain starting from `this.element` down to,
// but not including, [Object].
inheritanceChainElements ??=
inheritanceChain.map((c) => c.element).toList(growable: false);
final enclosingElement =
inheritenceElement.enclosingElement as InterfaceElement;
assert(inheritanceChainElements.contains(enclosingElement) ||
enclosingElement.isDartCoreObject);
// If the concrete object from
// [InheritanceManager3.getInheritedConcreteMap2] is farther from this
// class in the inheritance chain than the one provided by
// `inheritedMap2`, prefer `inheritedMap2`. This correctly accounts for
// intermediate abstract classes that have method/field implementations.
if (inheritanceChainElements.indexOf(
combinedMapElement.enclosingElement as InterfaceElement) <
inheritanceChainElements.indexOf(enclosingElement)) {
combinedMap[name.name] = inheritenceElement;
}
}
return combinedMap.values.toList(growable: false);
}();
/// All fields defined on this container, _including inherited fields_.
late final List<Field> allFields = () {
var inheritedAccessorElements = {
..._inheritedElements.whereType<PropertyAccessorElement>()
};
// 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<PropertyAccessorElement>>{};
for (var accessorElement in inheritedAccessorElements) {
accessorMap
.putIfAbsent(accessorElement.name.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.fields) {
var getterElement = field.getter;
if (getterElement == null && accessorMap.containsKey(field.name)) {
getterElement =
accessorMap[field.name]!.firstWhereOrNull((e) => e.isGetter);
}
var setterElement = field.setter;
if (setterElement == null && accessorMap.containsKey(field.name)) {
setterElement =
accessorMap[field.name]!.firstWhereOrNull((e) => e.isSetter);
}
fields.add(_createSingleField(
getterElement, setterElement, inheritedAccessorElements, field));
accessorMap.remove(field.name);
}
// 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.isGetter);
final setterElement = elements.firstWhereOrNull((e) => e.isSetter);
fields.add(_createSingleField(
getterElement, setterElement, inheritedAccessorElements));
});
return fields;
}();
@override
late final Iterable<Method> declaredMethods =
element.methods.map((e) => modelBuilder.from(e, library) as Method);
@override
late final List<TypeParameter> typeParameters = element.typeParameters
.map((typeParameter) => modelBuilder.from(
typeParameter,
modelBuilder.fromElement(typeParameter.enclosingElement!.library!)
as Library) as TypeParameter)
.toList(growable: false);
InheritingContainer(super.library, super.packageGraph);
@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 =>
modelBuilder.from(element, definingLibrary) as InheritingContainer;
@override
InterfaceElement get element;
@override
Library get enclosingElement => library;
String get fullkind => kind.toString();
@override
bool get hasModifiers =>
hasAnnotations ||
hasPublicSuperChainReversed ||
hasPotentiallyApplicableExtensions;
bool get hasPublicInheritedMethods => publicInheritedMethods.isNotEmpty;
bool get hasPublicSuperChainReversed => publicSuperChainReversed.isNotEmpty;
/// 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<InheritingContainer> get inheritanceChain;
Iterable<Field> get inheritedFields => allFields.where((f) => f.isInherited);
@override
Iterable<Field> get instanceFields => allFields.where((f) => !f.isStatic);
@override
Iterable<Method> get instanceMethods =>
[...super.instanceMethods, ...inheritedMethods];
@override
Iterable<Operator> get instanceOperators =>
[...super.instanceOperators, ...inheritedOperators];
bool get isAbstract;
bool get isBase;
@override
bool get isCanonical => super.isCanonical && isPublic;
@override
bool get isFinal;
bool get isInterface;
bool get isMixinClass;
bool get isSealed;
Iterable<Field> get publicInheritedFields =>
model_utils.filterNonPublic(inheritedFields);
@override
bool get publicInheritedInstanceFields =>
publicInstanceFields.every((f) => f.isInherited);
@override
bool get publicInheritedInstanceMethods =>
instanceMethods.every((f) => f.isInherited);
@override
bool get publicInheritedInstanceOperators =>
publicInstanceOperators.every((f) => f.isInherited);
Iterable<Method> get publicInheritedMethods =>
model_utils.filterNonPublic(inheritedMethods);
Iterable<DefinedElementType> get publicInterfaces => const [];
Iterable<DefinedElementType> get publicSuperChainReversed =>
publicSuperChain.reversed;
List<DefinedElementType> get superChain {
var typeChain = <DefinedElementType>[];
var parent = supertype;
while (parent != null) {
typeChain.add(parent);
final parentType = parent.type;
if (parentType is InterfaceType) {
// Avoid adding [Object] to the [superChain] ([_supertype] already has
// this check).
if (parentType.superclass?.superclass == null) {
break;
} else {
parent = modelBuilder.typeFrom(parentType.superclass!, library)
as DefinedElementType?;
}
} else {
parent = (parent.modelElement as Class).supertype;
}
}
return typeChain;
}
/// Add a single Field to _fields.
///
/// If [field] 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.
Field _createSingleField(
PropertyAccessorElement? getterElement,
PropertyAccessorElement? setterElement,
Set<PropertyAccessorElement> inheritedAccessors,
[FieldElement? field]) {
// Return a [ContainerAccessor] with `isInherited = true` if [element] is
// in [inheritedAccessors].
ContainerAccessor? containerAccessorFrom(PropertyAccessorElement? element) {
if (element == null) return null;
final enclosingContainer =
inheritedAccessors.contains(element) ? this : null;
return modelBuilder.from(element, library,
enclosingContainer: enclosingContainer) as ContainerAccessor;
}
var getter = containerAccessorFrom(getterElement);
var setter = containerAccessorFrom(setterElement);
// Rebind [getterElement], [setterElement] as [ModelElement.from] can
// resolve [MultiplyInheritedExecutableElement]s or 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!.variable as FieldElement;
} else if (getter == null && setter != null) {
field = setterElement!.variable as FieldElement;
} 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!.variable as FieldElement;
} else {
field = getterElement!.variable as FieldElement;
}
}
}
if ((getter == null || getter.isInherited) &&
(setter == null || setter.isInherited)) {
// Field is 100% inherited.
return modelBuilder.fromPropertyInducingElement(field, library,
enclosingContainer: this, getter: getter, setter: setter) 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 modelBuilder.fromPropertyInducingElement(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) => modelBuilder.typeFrom(f, library) as DefinedElementType)
.toList(growable: false);
@override
bool get hasModifiers => super.hasModifiers || hasPublicMixedInTypes;
bool get hasPublicMixedInTypes => publicMixedInTypes.isNotEmpty;
Iterable<DefinedElementType> get publicMixedInTypes =>
model_utils.filterNonPublic(mixedInTypes);
}
/// Add the ability for an [InheritingContainer] to be implemented by other
/// InheritingContainers and to reference what it itself implements.
mixin TypeImplementing on InheritingContainer {
late final List<DefinedElementType> directInterfaces = [
for (var interface in element.interfaces)
modelBuilder.typeFrom(interface, library) as DefinedElementType
];
late final List<InheritingContainer> publicImplementorsSorted =
publicImplementors.toList(growable: false)..sort(byName);
@override
bool get hasModifiers =>
super.hasModifiers || hasPublicInterfaces || hasPublicImplementors;
bool get hasPublicImplementors => publicImplementors.isNotEmpty;
bool get hasPublicInterfaces => publicInterfaces.isNotEmpty;
/// Interfaces directly implemented by this container.
List<DefinedElementType> get interfaces => directInterfaces;
/// Returns all the "immediate" public implementors of this
/// [TypeImplementing]. For a [Mixin], this is actually the mixin
/// applications using the [Mixin].
///
/// If this [InheritingContainer] has a private implementor, then that is
/// counted as a proxy for any public implementors of that private container.
Iterable<InheritingContainer> get publicImplementors {
var result = <InheritingContainer>{};
var seen = <InheritingContainer>{};
// Recursively adds [implementor] if public, or the implementors of
// [implementor] if not.
void addToResult(InheritingContainer implementor) {
if (seen.contains(implementor)) return;
seen.add(implementor);
if (implementor.isPublicAndPackageDocumented) {
result.add(implementor);
} else {
model_utils
.findCanonicalFor(
packageGraph.implementors[implementor] ?? const [])
.forEach(addToResult);
}
}
model_utils
.findCanonicalFor(packageGraph.implementors[this] ?? const [])
.forEach(addToResult);
return result;
}
/// The public interfaces may include substitutions for intermediate
/// private interfaces, and so unlike other public* methods, is not
/// a strict subset of [interfaces].
@override
Iterable<DefinedElementType> get publicInterfaces sync* {
for (var i in directInterfaces) {
/// Do not recurse if we can find an element here.
if (i.modelElement.canonicalModelElement != null) {
yield i;
continue;
}
// Public types used to be unconditionally exposed here. However,
// if the packages are [DocumentLocation.missing] we generally treat types
// defined in them as actually defined in a documented package.
// That translates to them being defined here, but in 'src/' or similar,
// and so, are now always hidden.
// This type is not backed by a canonical Class; search
// the superchain and publicInterfaces of this interface to pretend
// as though the hidden class didn't exist and this class was declared
// directly referencing the canonical classes further up the chain.
if (i.modelElement is InheritingContainer) {
var hiddenContainer = i.modelElement as InheritingContainer;
if (hiddenContainer.publicSuperChain.isNotEmpty) {
yield hiddenContainer.publicSuperChain.first;
}
yield* hiddenContainer.publicInterfaces;
} else {
assert(
false,
'Can not handle intermediate non-public interfaces created by '
'ModelElements that are not classes or mixins: $fullyQualifiedName '
'contains an interface $i, defined by ${i.modelElement}');
continue;
}
}
}
}
extension on InterfaceElement {
bool get isDartCoreObject => name == 'Object' && library.name == 'dart.core';
}
extension DefinedElementTypeIterableExtensions on Iterable<DefinedElementType> {
/// Expands the `ModelElement` for each element to its inheritance chain.
Iterable<InheritingContainer> get expandInheritanceChain =>
expand((e) => (e.modelElement as InheritingContainer).inheritanceChain);
/// Returns the `ModelElement` for each element.
Iterable<InheritingContainer> get modelElements =>
map((e) => e.modelElement as InheritingContainer);
}