blob: d5eb337b47afab0760353ea96eebcb55d097501c [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/element2.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:dartdoc/src/model/attribute.dart';
import 'package:dartdoc/src/model/comment_referable.dart';
import 'package:dartdoc/src/model/model.dart';
/// Mixin for subclasses of [ModelElement] representing elements that can be
/// inherited from one class to another.
///
/// We can search the inheritance chain between this instance and
/// [definingEnclosingContainer] in [Inheritable.canonicalEnclosingContainer],
/// for the canonical [Class] closest to where this member was defined. We
/// can then know that when we find [Inheritable.element] inside that [Class]'s
/// namespace, that's the one we should treat as canonical and implementors of
/// this class can use that knowledge to determine canonicalization.
///
/// We pick the class closest to the [definingEnclosingContainer] so that all
/// children of that class inheriting the same member will point to the same
/// place in the documentation, and we pick a canonical class because that's
/// the one in the public namespace that will be documented.
mixin Inheritable on ContainerMember {
/// Whether this is inherited from a different class or mixin.
bool get isInherited;
/// Whether this has a parameter whose type is overridden by a subtype.
bool get isCovariant;
@override
Set<Attribute> get attributes => {
...super.attributes,
if (isOverride) Attribute.override_,
if (isInherited) Attribute.inherited,
if (isCovariant) Attribute.covariant,
};
@override
Library? get canonicalLibrary =>
canonicalEnclosingContainer?.canonicalLibrary;
@override
// TODO(srawlins): Do something about this overridden field. Maybe split out
// the super implementation.
// ignore: overridden_fields
late final ModelElement? canonicalModelElement = canonicalEnclosingContainer
?.allCanonicalModelElements
.firstWhereOrNull((m) =>
m.name == name &&
m is PropertyAccessorElement2 == this is PropertyAccessorElement2);
@override
Container? computeCanonicalEnclosingContainer() {
if (!isInherited || definingEnclosingContainer is Extension) {
return super.computeCanonicalEnclosingContainer();
}
var searchElement = element.baseElement;
// TODO(jcollins-g): generate warning if an inherited element's definition
// is in an intermediate non-canonical class in the inheritance chain?
var reverseInheritance = _inheritance.reversed.toList();
for (var i = 0; i < reverseInheritance.length; i++) {
var container = reverseInheritance[i];
if (container.containsElement(searchElement)) {
var previousIsHiddenAndNotDefining = i > 0 &&
_isHiddenInterface(reverseInheritance[i - 1]) &&
container != definingEnclosingContainer;
var thisIsHiddenAndDefining = _isHiddenInterface(container) &&
container == definingEnclosingContainer;
// If the previous container in the search is one of the "hidden"
// interfaces, and it's not this member's defining container, OR if this
// container in the search is one of the "hidden" interfaces, and it is
// also this member's defining container, then we can immediately return
// the canonical enclosing container of the overridden member in the
// previous, non-hidden container in the inheritance.
if (previousIsHiddenAndNotDefining || thisIsHiddenAndDefining) {
var previousVisible = reverseInheritance
.take(i)
.lastWhere((e) => !_isHiddenInterface(e));
var membersInPreviousVisible = previousVisible.allModelElements
.where((e) => e.name == name)
.whereType<Inheritable>()
.whereNotType<Field>();
assert(
membersInPreviousVisible.length == 1,
'found multiple members named "$name" in '
'"${previousVisible.name}": '
'${membersInPreviousVisible.toList()}');
return membersInPreviousVisible.first.canonicalEnclosingContainer;
}
var canonicalContainer =
packageGraph.findCanonicalModelElementFor(container) as Container?;
// TODO(jcollins-g): invert this lookup so traversal is recursive
// starting from the ModelElement.
if (canonicalContainer != null) {
return canonicalContainer;
}
}
}
// None of the super-containers are canonical.
return null;
}
/// Whether [c] is a "hidden" interface.
///
/// A hidden interface should never be considered the canonical enclosing
/// container of a container member.
///
/// Add classes here if they are similar to the Dart SDK's 'Interceptor' class
/// in that they are to be ignored even when they are the implementers of
/// [Inheritable]s, and the class these inherit from should instead claim
/// implementation.
bool _isHiddenInterface(Container? c) =>
c != null &&
c.element.name3 == 'Interceptor' &&
c.element.library2?.name3 == '_interceptors';
/// A roughly ordered list of this element's enclosing container's inheritance
/// chain.
///
/// See [InheritingContainer.inheritanceChain] for details.
List<InheritingContainer> get _inheritance {
var inheritance = [
...(enclosingElement as InheritingContainer).inheritanceChain,
];
assert(
definingEnclosingContainer.isDartCoreObject ||
inheritance.contains(definingEnclosingContainer), () {
var inheritanceDescriptions = inheritance
.map((e) =>
"'$e' (hashCode: ${e.hashCode}, in library '${e.library}')")
.toList();
return "Given '$this', on '$enclosingElement' in library '$library', "
"the defining enclosing container, '$definingEnclosingContainer' "
'(hashCode: ${definingEnclosingContainer.hashCode}, '
"in library '${definingEnclosingContainer.library}'), should have "
'been Object or contained in: $inheritanceDescriptions';
}());
// Unless the code explicitly extends dart:core's Object, we won't get
// an entry here. So add it.
if (!inheritance.last.isDartCoreObject) {
inheritance.add(packageGraph.objectClass);
}
return inheritance;
}
Inheritable? get overriddenElement;
/// Whether this [Inheritable] is overriding a member from a superclass.
///
/// This is distinct from [isInherited]. An inheritable member which is an
/// override is explicitly written in its container. An inheritable member
/// which is implicitly included in a container is "inherited", and not an
/// override.
late final bool isOverride = () {
// The canonical version of the enclosing element -- not
// [canonicalEnclosingElement], as that is the element enclosing the
// canonical version of this element; two different things. Defaults to the
// enclosing element.
//
// We use canonical elements here where possible to deal with reexports
// as seen in Flutter.
if (enclosingElement is Extension) {
return false;
}
final overriddenElement = this.overriddenElement;
if (overriddenElement == null) {
// We have to have an overridden element for it to be possible for this
// element to be an override.
return false;
}
final enclosingCanonical =
enclosingElement.canonicalModelElement as InheritingContainer?;
// The container in which this element was defined, canonical if available.
final definingCanonical =
definingEnclosingContainer.canonicalModelElement as Container? ??
definingEnclosingContainer;
if (enclosingCanonical != definingCanonical) {
// The defining class and the enclosing class for this element must be the
// same (element is defined here).
assert(isInherited);
return false;
}
// The canonical version of the element we're overriding, if available.
final overriddenCanonical =
overriddenElement.canonicalModelElement ?? overriddenElement;
// If the overridden element isn't public, we shouldn't be an override in
// most cases. Approximation until #1623 is fixed.
final isOverride = overriddenCanonical.isPublic;
assert(!isOverride || !isInherited);
return isOverride;
}();
/// The depth of overrides at which this element lives.
///
/// Just a count of how long the chain of this element's `overriddenElement`.
/// For use in ranking search results.
int get overriddenDepth {
var depth = 0;
var e = this;
while (e.overriddenElement != null) {
depth += 1;
e = e.overriddenElement!;
}
return depth;
}
}