| // 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:dartdoc/src/element_type.dart'; |
| import 'package:dartdoc/src/model/comment_referable.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 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 |
| /// [hasModifier] override is not necessary for this mixin. |
| mixin Constructable on InheritingContainer { |
| List<Constructor> _constructors; |
| Iterable<Constructor> get constructors => _constructors ??= [ |
| ...element.constructors |
| .map((e) => modelBuilder.from(e, library) as Constructor) |
| ]; |
| |
| @override |
| bool get hasPublicConstructors => publicConstructorsSorted.isNotEmpty; |
| |
| @visibleForTesting |
| Iterable<Constructor> get publicConstructors => |
| model_utils.filterNonPublic(constructors); |
| |
| List<Constructor> _publicConstructorsSorted; |
| |
| @override |
| Iterable<Constructor> get publicConstructorsSorted => |
| _publicConstructorsSorted ??= publicConstructors.toList()..sort(byName); |
| |
| Constructor _unnamedConstructor; |
| Constructor get unnamedConstructor { |
| _unnamedConstructor ??= constructors |
| .firstWhere((c) => c.isUnnamedConstructor, orElse: () => null); |
| return _unnamedConstructor; |
| } |
| |
| Constructor _defaultConstructor; |
| |
| /// With constructor tearoffs, this is no longer equivalent to the unnamed |
| /// constructor and assumptions based on that are incorrect. |
| Constructor get defaultConstructor { |
| _defaultConstructor ??= unnamedConstructor ?? |
| constructors.firstWhere((c) => c.isDefaultConstructor); |
| return _defaultConstructor; |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| @override |
| 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 Class) { |
| yield* _constructorGenerator(container.constructors); |
| } |
| } |
| } |
| } |
| |
| /// Add the ability to support mixed-in types to an [InheritingContainer]. |
| mixin MixedInTypes on InheritingContainer { |
| List<DefinedElementType> _mixedInTypes; |
| |
| List<DefinedElementType> get mixedInTypes => |
| _mixedInTypes ?? |
| [ |
| ...element.mixins |
| .map<DefinedElementType>((f) => modelBuilder.typeFrom(f, library)) |
| .where((mixin) => mixin != null) |
| ]; |
| |
| bool get hasPublicMixedInTypes => publicMixedInTypes.isNotEmpty; |
| |
| @override |
| bool get hasModifiers => super.hasModifiers || hasPublicMixedInTypes; |
| |
| 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 { |
| List<DefinedElementType> _directInterfaces; |
| List<DefinedElementType> get directInterfaces => |
| _directInterfaces ?? |
| [ |
| ...element.interfaces |
| .map<DefinedElementType>((f) => modelBuilder.typeFrom(f, library)) |
| .toList(growable: false) |
| ]; |
| |
| /// Interfaces directly implemented by this container. |
| List<DefinedElementType> get interfaces => directInterfaces; |
| |
| bool get hasPublicInterfaces => publicInterfaces.isNotEmpty; |
| |
| @override |
| bool get hasModifiers => |
| super.hasModifiers || hasPublicInterfaces || hasPublicImplementors; |
| |
| /// 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; |
| } |
| } |
| } |
| |
| bool get hasPublicImplementors => publicImplementors.isNotEmpty; |
| |
| /// 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] ?? []) |
| .forEach(addToResult); |
| } |
| } |
| |
| model_utils |
| .findCanonicalFor(packageGraph.implementors[this] ?? []) |
| .forEach(addToResult); |
| return result; |
| } |
| |
| List<InheritingContainer> _publicImplementorsSorted; |
| |
| Iterable<InheritingContainer> get publicImplementorsSorted => |
| _publicImplementorsSorted ??= publicImplementors.toList()..sort(byName); |
| } |
| |
| /// 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 { |
| @override |
| |
| /// [ClassElement] is analogous to [InheritingContainer]. |
| ClassElement get element => super.element; |
| |
| DefinedElementType _supertype; |
| DefinedElementType get supertype => |
| _supertype ??= element.supertype?.element?.supertype == null |
| ? null |
| : modelBuilder.typeFrom(element.supertype, library); |
| |
| InheritingContainer( |
| ClassElement element, Library library, PackageGraph packageGraph) |
| : super(element, library, packageGraph); |
| |
| @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]); |
| |
| @override |
| bool get publicInheritedInstanceOperators => |
| publicInstanceOperators.every((f) => f.isInherited); |
| |
| List<ModelElement> _allModelElements; |
| |
| @override |
| List<ModelElement> get allModelElements { |
| _allModelElements ??= List.from( |
| quiver.concat<ModelElement>([ |
| super.allModelElements, |
| typeParameters, |
| ]), |
| growable: false); |
| return _allModelElements; |
| } |
| |
| /// Returns the [InheritingContainer] with the library in which [element] is defined. |
| InheritingContainer get definingContainer => |
| modelBuilder.from(element, definingLibrary); |
| |
| /// Returns the library that encloses this element. |
| @override |
| ModelElement get enclosingElement => library; |
| |
| @override |
| String get filePath => '${library.dirName}/$fileName'; |
| |
| String get fullkind => kind; |
| |
| @override |
| bool get hasModifiers => |
| hasAnnotations || |
| hasPublicSuperChainReversed || |
| 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'; |
| } |
| |
| /*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 = modelBuilder.from(e, library, 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 = modelBuilder.from(e, library, enclosingContainer: this); |
| _inheritedOperators.add(o); |
| } |
| } |
| return _inheritedOperators; |
| } |
| |
| Iterable<Field> get inheritedFields => allFields.where((f) => f.isInherited); |
| |
| Iterable<DefinedElementType> get publicInterfaces => []; |
| |
| Iterable<Field> get publicInheritedFields => |
| model_utils.filterNonPublic(inheritedFields); |
| |
| @override |
| bool get isCanonical => super.isCanonical && isPublic; |
| |
| /// Returns true if [other] is a parent class for this class. |
| bool _isInheritingFrom(InheritingContainer other) => superChain |
| .map((et) => (et.modelElement as InheritingContainer)) |
| .contains(other); |
| |
| DefinedElementType _modelType; |
| |
| @override |
| DefinedElementType get modelType => |
| _modelType ??= modelBuilder.typeFrom(element.thisType, library); |
| |
| /// 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; |
| |
| 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 = modelBuilder.typeFrom( |
| (parent.type as InterfaceType).superclass, library); |
| } |
| } else { |
| parent = (parent.modelElement 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); |
| |
| List<ClassElement> inheritanceChainElements; |
| |
| var combinedMap = <String, ExecutableElement>{}; |
| for (var nameObj in cmap.keys) { |
| combinedMap[nameObj.name] = cmap[nameObj]; |
| } |
| for (var nameObj in imap.keys) { |
| if (combinedMap[nameObj.name] != null) { |
| // Elements in the inheritance chain starting from [this.element] |
| // down to, but not including, [Object]. |
| inheritanceChainElements ??= |
| inheritanceChain.map((c) => c.element).toList(); |
| // [packageGraph.specialClasses] is not available yet. |
| bool _isDartCoreObject(ClassElement e) => |
| e.name == 'Object' && e.library.name == 'dart.core'; |
| assert(inheritanceChainElements |
| .contains(imap[nameObj].enclosingElement) || |
| _isDartCoreObject(imap[nameObj].enclosingElement)); |
| |
| // 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(combinedMap[nameObj.name].enclosingElement) < |
| inheritanceChainElements |
| .indexOf(imap[nameObj].enclosingElement)) { |
| combinedMap[nameObj.name] = imap[nameObj]; |
| } |
| } else { |
| 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]) { |
| /// Return an [ContainerAccessor] with isInherited = true |
| /// if [element] is in [inheritedAccessors]. |
| ContainerAccessor containerAccessorFrom( |
| PropertyAccessorElement element, |
| Set<PropertyAccessorElement> inheritedAccessors, |
| Container enclosingContainer) { |
| ContainerAccessor accessor; |
| if (element == null) return null; |
| if (inheritedAccessors.contains(element)) { |
| accessor = modelBuilder.from(element, enclosingContainer.library, |
| enclosingContainer: enclosingContainer); |
| } else { |
| accessor = modelBuilder.from(element, enclosingContainer.library); |
| } |
| return accessor; |
| } |
| |
| var getter = containerAccessorFrom(getterElement, inheritedAccessors, this); |
| var setter = containerAccessorFrom(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 = modelBuilder.fromPropertyInducingElement(f, library, |
| 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 = modelBuilder.fromPropertyInducingElement(f, library, |
| getter: getter, setter: setter); |
| } |
| _allFields.add(field); |
| } |
| |
| Iterable<Method> _declaredMethods; |
| |
| @override |
| Iterable<Method> get declaredMethods => |
| _declaredMethods ??= element.methods.map((e) { |
| return modelBuilder.from(e, library) as Method; |
| }); |
| |
| List<TypeParameter> _typeParameters; |
| |
| @override |
| List<TypeParameter> get typeParameters { |
| _typeParameters ??= element.typeParameters.map((f) { |
| var lib = modelBuilder.fromElement(f.enclosingElement.library); |
| return modelBuilder.from(f, lib) 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); |
| } |