blob: cbac2462dcd43f23b74152b0a780349a3d4ffce9 [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/scope.dart';
import 'package:dartdoc/src/model/comment_referable.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/model_utils.dart' as model_utils;
import 'package:meta/meta.dart';
/// A [Container] represents a Dart construct that can contain methods,
/// operators, and fields, such as [Class], [Enum], or [Extension].
/// Member naming in [Container] follows these general rules:
/// * **instance** : children of this container that can be referenced from
/// within the container without a prefix. These are usually overridden in
/// subclasses with calls to 'super'.
/// * **constant** : children declared constant.
/// * **variable** : The opposite of constant. These are available for the
/// templating system.
/// * **static** : static children of this container.
/// * **public** : Filtered versions of the above members, containing only
/// public items. These are available mostly for the templating system.
/// * **sorted** : Filtered versions of the above members as a list sorted by
/// name. These are available for the templating system.
/// * **has** : boolean getters indicating whether the underlying collections
/// are empty. These are available mostly for the templating system.
abstract class Container extends ModelElement
with Categorization, TypeParameters, HideConstantImplementations {
Container(super.library, super.packageGraph);
// TODO(jcollins-g): Implement a ContainerScope that flattens supertypes?
Scope? get scope => null;
bool get hasParameters => false;
bool get isExtension => element is ExtensionElement;
/// Whether this is an interface type (class, enum, mixin, extension type) or
/// an extension.
/// For templates, interfaces and extensions have much in common despite
/// differing underlying implementations in the analyzer.
bool get isInterfaceOrExtension => element is InterfaceElement || isExtension;
/// Whether this is an enum.
bool get isEnum => element is EnumElement;
/// Whether this is a class or an enum.
bool get isClassOrEnum => element is InterfaceElement;
/// Whether this is a mixin.
bool get isMixin => element is MixinElement;
Iterable<ModelElement> get allModelElements => [
late final List<ModelElement> allCanonicalModelElements =
allModelElements.where((e) => e.isCanonical).toList(growable: false);
/// All methods, including operators and statics, declared as part of this
/// [Container].
/// [declaredMethods] must be the union of [instanceMethods],
/// [staticMethods], and [instanceOperators].
Iterable<Method> get declaredMethods;
Iterable<Method> get instanceMethods => declaredMethods
.where((m) => !m.isStatic && !m.isOperator)
.toList(growable: false);
/// Whether all instance fields are inherited.
bool get publicInheritedInstanceFields => false;
/// Whether all instance methods are inherited.
bool get publicInheritedInstanceMethods => false;
/// Whether all instance operators are inherited.
bool get publicInheritedInstanceOperators => false;
/// Override if this is [Constructable].
bool get hasPublicConstructors => false;
List<Constructor> get publicConstructorsSorted => const [];
bool get hasPublicInstanceMethods =>
Iterable<Method> get publicInstanceMethods =>
late final List<Method> publicInstanceMethodsSorted =
publicInstanceMethods.toList(growable: false)..sort();
late final Iterable<Operator> declaredOperators =
declaredMethods.whereType<Operator>().toList(growable: false);
ModelElement get enclosingElement;
Iterable<Operator> get instanceOperators => declaredOperators;
bool get hasPublicInstanceOperators =>
Iterable<Operator> get publicInstanceOperators =>
late final List<Operator> publicInstanceOperatorsSorted =
publicInstanceOperators.toList(growable: false)..sort();
/// Fields fully declared in this [Container].
Iterable<Field> get declaredFields;
/// All instance fields declared in this [Container].
Iterable<Field> get instanceFields =>
declaredFields.where((f) => !f.isStatic);
bool get hasInstanceFields => instanceFields.isNotEmpty;
Iterable<Field> get publicInstanceFields =>
bool get hasPublicInstanceFields => publicInstanceFields.isNotEmpty;
late final List<Field> publicInstanceFieldsSorted =
publicInstanceFields.toList(growable: false)..sort(byName);
Iterable<Field> get constantFields => declaredFields.where((f) => f.isConst);
Iterable<Field> get publicConstantFields =>
bool get hasPublicConstantFields => publicConstantFields.isNotEmpty;
late final List<Field> publicConstantFieldsSorted =
publicConstantFields.toList(growable: false)..sort(byName);
Iterable<Field> get publicEnumValues => const [];
bool get hasPublicEnumValues => publicEnumValues.isNotEmpty;
Iterable<Accessor> get instanceAccessors =>
instanceFields.expand((f) => f.allAccessors);
Iterable<Accessor> get staticAccessors =>
staticFields.expand((f) => f.allAccessors);
/// This container might be canonical for elements it does not contain.
/// See [Inheritable.canonicalEnclosingContainer].
bool containsElement(Element? element) => allElements.contains(element);
late final Set<Element?> allElements = => e.element).toSet();
late final Map<String, List<ModelElement>> _membersByName = () {
var membersByName = <String, List<ModelElement>>{};
for (var element in allModelElements) {
membersByName.putIfAbsent(, () => []).add(element);
return membersByName;
/// Given a ModelElement that is a member of some other class, returns
/// the member of this class that has the same name and runtime type.
/// This enables object substitution for canonicalization, such as Interceptor
/// for Object.
T memberByExample<T extends ModelElement>(T example) {
ModelElement member;
// [T] is insufficiently specific to disambiguate between different
// subtypes of [Inheritable] or other mixins/implementations of
// [ModelElement] via [Iterable.whereType].
var possibleMembers = _membersByName[]!
.where((e) => e.runtimeType == example.runtimeType);
if (example is Accessor) {
possibleMembers = possibleMembers
.where((e) => example.isGetter == (e as Accessor).isGetter);
member = possibleMembers.first;
assert(possibleMembers.length == 1);
return member as T;
bool get hasPublicStaticFields => publicStaticFieldsSorted.isNotEmpty;
late final List<Field> publicStaticFieldsSorted =
model_utils.filterNonPublic(staticFields).toList(growable: false)..sort();
Iterable<Field> get staticFields => declaredFields.where((f) => f.isStatic);
Iterable<Field> get variableStaticFields =>
staticFields.where((f) => !f.isConst);
bool get hasPublicVariableStaticFields =>
late final List<Field> publicVariableStaticFieldsSorted = model_utils
.toList(growable: false)
Iterable<Method> get staticMethods =>
declaredMethods.where((m) => m.isStatic);
bool get hasPublicStaticMethods =>
late final List<Method> publicStaticMethodsSorted = model_utils
.toList(growable: false)
/// For subclasses to add items after the main pass but before the
/// parameter-global.
Iterable<MapEntry<String, CommentReferable>> get extraReferenceChildren;
late final Map<String, CommentReferable> referenceChildren = () {
var referenceChildren = <String, CommentReferable>{
for (var element in allModelElements
element.referenceName: element,
// Process unscoped parameters last to make sure they don't override
// other options.
for (var modelElement in allModelElements) {
// Don't complain about references to parameter names, but prefer
// referring to anything else.
// TODO(jcollins-g): Figure out something good to do in the ecosystem
// here to wean people off the habit of unscoped parameter references.
if (modelElement.hasParameters) {
referenceChildren['this'] = this;
return referenceChildren;
Iterable<CommentReferable> get referenceParents => [definingLibrary, library];
String get filePath => '${library.dirName}/${fileStructure.fileName}';
/// The full path of this element's sidebar file.
String get sidebarPath;
String get aboveSidebarPath => library.sidebarPath;
String get belowSidebarPath => sidebarPath;
/// The CSS class to use in an inheritance list.
String get relationshipsClass;