diff --git a/lib/src/generator/templates.runtime_renderers.dart b/lib/src/generator/templates.runtime_renderers.dart index 2b1a9253f6..ff6b1b5755 100644 --- a/lib/src/generator/templates.runtime_renderers.dart +++ b/lib/src/generator/templates.runtime_renderers.dart @@ -1673,28 +1673,6 @@ class _Renderer_Class extends RendererBase { parent: r); }, ), - 'fullkind': Property( - getValue: (CT_ c) => c.fullkind, - renderVariable: - (CT_ c, Property self, List remainingNames) { - if (remainingNames.isEmpty) { - return self.getValue(c).toString(); - } - var name = remainingNames.first; - var nextProperty = - _Renderer_String.propertyMap().getValue(name); - return nextProperty.renderVariable( - self.getValue(c) as String, - nextProperty, - [...remainingNames.skip(1)]); - }, - isNullValue: (CT_ c) => false, - renderValue: (CT_ c, RendererBase r, - List ast, StringSink sink) { - _render_String(c.fullkind, ast, r.template, sink, - parent: r); - }, - ), 'inheritanceChain': Property( getValue: (CT_ c) => c.inheritanceChain, renderVariable: (CT_ c, Property self, @@ -1715,6 +1693,13 @@ class _Renderer_Class extends RendererBase { self.renderSimpleVariable(c, remainingNames, 'bool'), getBool: (CT_ c) => c.isAbstract == true, ), + 'isBase': Property( + getValue: (CT_ c) => c.isBase, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isBase == true, + ), 'isErrorOrException': Property( getValue: (CT_ c) => c.isErrorOrException, renderVariable: (CT_ c, Property self, @@ -1722,6 +1707,34 @@ class _Renderer_Class extends RendererBase { self.renderSimpleVariable(c, remainingNames, 'bool'), getBool: (CT_ c) => c.isErrorOrException == true, ), + 'isFinal': Property( + getValue: (CT_ c) => c.isFinal, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isFinal == true, + ), + 'isInterface': Property( + getValue: (CT_ c) => c.isInterface, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isInterface == true, + ), + 'isMixinClass': Property( + getValue: (CT_ c) => c.isMixinClass, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isMixinClass == true, + ), + 'isSealed': Property( + getValue: (CT_ c) => c.isSealed, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isSealed == true, + ), 'kind': Property( getValue: (CT_ c) => c.kind, renderVariable: @@ -4358,6 +4371,41 @@ class _Renderer_Enum extends RendererBase { parent: r)); }, ), + 'isAbstract': Property( + getValue: (CT_ c) => c.isAbstract, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isAbstract == true, + ), + 'isBase': Property( + getValue: (CT_ c) => c.isBase, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isBase == true, + ), + 'isInterface': Property( + getValue: (CT_ c) => c.isInterface, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isInterface == true, + ), + 'isMixinClass': Property( + getValue: (CT_ c) => c.isMixinClass, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isMixinClass == true, + ), + 'isSealed': Property( + getValue: (CT_ c) => c.isSealed, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isSealed == true, + ), 'kind': Property( getValue: (CT_ c) => c.kind, renderVariable: @@ -6971,6 +7019,20 @@ class _Renderer_InheritingContainer extends RendererBase { _render_Operator(e, ast, r.template, sink, parent: r)); }, ), + 'isAbstract': Property( + getValue: (CT_ c) => c.isAbstract, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isAbstract == true, + ), + 'isBase': Property( + getValue: (CT_ c) => c.isBase, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isBase == true, + ), 'isCanonical': Property( getValue: (CT_ c) => c.isCanonical, renderVariable: (CT_ c, Property self, @@ -6978,6 +7040,34 @@ class _Renderer_InheritingContainer extends RendererBase { self.renderSimpleVariable(c, remainingNames, 'bool'), getBool: (CT_ c) => c.isCanonical == true, ), + 'isFinal': Property( + getValue: (CT_ c) => c.isFinal, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isFinal == true, + ), + 'isInterface': Property( + getValue: (CT_ c) => c.isInterface, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isInterface == true, + ), + 'isMixinClass': Property( + getValue: (CT_ c) => c.isMixinClass, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isMixinClass == true, + ), + 'isSealed': Property( + getValue: (CT_ c) => c.isSealed, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isSealed == true, + ), 'modelType': Property( getValue: (CT_ c) => c.modelType, renderVariable: @@ -7002,6 +7092,28 @@ class _Renderer_InheritingContainer extends RendererBase { parent: r); }, ), + 'modifiers': Property( + getValue: (CT_ c) => c.modifiers, + renderVariable: + (CT_ c, Property self, List remainingNames) { + if (remainingNames.isEmpty) { + return self.getValue(c).toString(); + } + var name = remainingNames.first; + var nextProperty = + _Renderer_String.propertyMap().getValue(name); + return nextProperty.renderVariable( + self.getValue(c) as String, + nextProperty, + [...remainingNames.skip(1)]); + }, + isNullValue: (CT_ c) => false, + renderValue: (CT_ c, RendererBase r, + List ast, StringSink sink) { + _render_String(c.modifiers, ast, r.template, sink, + parent: r); + }, + ), 'publicInheritedFields': Property( getValue: (CT_ c) => c.publicInheritedFields, renderVariable: (CT_ c, Property self, @@ -9377,6 +9489,48 @@ class _Renderer_Mixin extends RendererBase { parent: r)); }, ), + 'isAbstract': Property( + getValue: (CT_ c) => c.isAbstract, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isAbstract == true, + ), + 'isBase': Property( + getValue: (CT_ c) => c.isBase, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isBase == true, + ), + 'isFinal': Property( + getValue: (CT_ c) => c.isFinal, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isFinal == true, + ), + 'isInterface': Property( + getValue: (CT_ c) => c.isInterface, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isInterface == true, + ), + 'isMixinClass': Property( + getValue: (CT_ c) => c.isMixinClass, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isMixinClass == true, + ), + 'isSealed': Property( + getValue: (CT_ c) => c.isSealed, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.isSealed == true, + ), 'kind': Property( getValue: (CT_ c) => c.kind, renderVariable: diff --git a/lib/src/model/class.dart b/lib/src/model/class.dart index 61199b315d..e4d740d809 100644 --- a/lib/src/model/class.dart +++ b/lib/src/model/class.dart @@ -17,22 +17,42 @@ class Class extends InheritingContainer @override final ClassElement element; - Class(this.element, Library library, PackageGraph packageGraph) - : super(library, packageGraph) { - packageGraph.specialClasses.addSpecial(this); - } - @override late final List allModelElements = [ ...super.allModelElements, ...constructors, ]; + @override + late final List inheritanceChain = [ + this, + + // Caching should make this recursion a little less painful. + for (var container in mixedInTypes.reversed.modelElements) + ...container.inheritanceChain, + + for (var container in superChain.modelElements) + ...container.inheritanceChain, + + // Interfaces need to come last, because classes in the superChain might + // implement them even when they aren't mentioned. + ...interfaces.expandInheritanceChain, + ]; + + Class(this.element, Library library, PackageGraph packageGraph) + : super(library, packageGraph) { + packageGraph.specialClasses.addSpecial(this); + } + @override String get fileName => '$name-class.$fileType'; + @override bool get isAbstract => element.isAbstract; + @override + bool get isBase => element.isBase; + bool get isErrorOrException { bool isError(InterfaceElement element) => element.library.isDartCore && @@ -44,29 +64,19 @@ class Class extends InheritingContainer } @override - String get kind => 'class'; + bool get isFinal => element.isFinal; @override - String get fullkind { - if (isAbstract) return 'abstract $kind'; - return super.fullkind; - } + bool get isInterface => element.isInterface; @override - late final List inheritanceChain = [ - this, + bool get isMixinClass => element.isMixinClass; - // Caching should make this recursion a little less painful. - for (var container in mixedInTypes.reversed.modelElements) - ...container.inheritanceChain, - - for (var container in superChain.modelElements) - ...container.inheritanceChain, + @override + bool get isSealed => element.isSealed; - // Interfaces need to come last, because classes in the superChain might - // implement them even when they aren't mentioned. - ...interfaces.expandInheritanceChain, - ]; + @override + String get kind => 'class'; @override String get relationshipsClass => 'clazz-relationships'; diff --git a/lib/src/model/enum.dart b/lib/src/model/enum.dart index 1213dacf75..93943332ef 100644 --- a/lib/src/model/enum.dart +++ b/lib/src/model/enum.dart @@ -8,6 +8,9 @@ import 'package:dartdoc/src/model/model.dart'; import 'package:dartdoc/src/model_utils.dart' as model_utils; import 'package:dartdoc/src/render/enum_field_renderer.dart'; +/// The [Enum] class only inherits from [InheritingContainer] because declared +/// `enum`s inherit methods from [Object]. It can't actually participate +/// meaningfully in other inheritance or have class modifiers. class Enum extends InheritingContainer with Constructable, TypeImplementing, MixedInTypes { @override @@ -47,6 +50,21 @@ class Enum extends InheritingContainer @override bool get hasPublicEnumValues => publicEnumValues.isNotEmpty; + + @override + bool get isAbstract => false; + + @override + bool get isBase => false; + + @override + bool get isInterface => false; + + @override + bool get isMixinClass => false; + + @override + bool get isSealed => false; } /// A field specific to an enum's values. diff --git a/lib/src/model/inheriting_container.dart b/lib/src/model/inheriting_container.dart index fd814df6f1..dd98b4987e 100644 --- a/lib/src/model/inheriting_container.dart +++ b/lib/src/model/inheriting_container.dart @@ -22,26 +22,10 @@ mixin Constructable on InheritingContainer { .map((e) => modelBuilder.from(e, library) as Constructor) .toList(growable: false); - @override - bool get hasPublicConstructors => publicConstructorsSorted.isNotEmpty; - @override late final List publicConstructorsSorted = model_utils.filterNonPublic(constructors).toList(growable: false)..sort(); - static Iterable> _constructorGenerator( - Iterable 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 @visibleForOverriding Iterable> @@ -62,113 +46,22 @@ mixin Constructable on InheritingContainer { } } } -} - -/// Add the ability to support mixed-in types to an [InheritingContainer]. -mixin MixedInTypes on InheritingContainer { - late final List mixedInTypes = element.mixins - .map((f) => modelBuilder.typeFrom(f, library) as DefinedElementType) - .toList(growable: false); - - bool get hasPublicMixedInTypes => publicMixedInTypes.isNotEmpty; - - @override - bool get hasModifiers => super.hasModifiers || hasPublicMixedInTypes; - - Iterable 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 directInterfaces = [ - for (var interface in element.interfaces) - modelBuilder.typeFrom(interface, library) as DefinedElementType - ]; - - /// Interfaces directly implemented by this container. - List 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 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. + bool get hasPublicConstructors => publicConstructorsSorted.isNotEmpty; - // 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; + static Iterable> _constructorGenerator( + Iterable 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); } } } - - 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 get publicImplementors { - var result = {}; - var seen = {}; - - // 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; - } - - late final List publicImplementorsSorted = - publicImplementors.toList(growable: false)..sort(byName); } /// A [Container] that participates in inheritance in Dart. @@ -181,9 +74,6 @@ mixin TypeImplementing on InheritingContainer { abstract class InheritingContainer extends Container with ExtensionTarget implements EnclosedElement { - @override - InterfaceElement get element; - late final DefinedElementType? supertype = () { final elementSupertype = element.supertype; return elementSupertype == null || @@ -193,49 +83,25 @@ abstract class InheritingContainer extends Container as DefinedElementType; }(); - InheritingContainer(super.library, super.packageGraph); - - @override - Iterable get instanceMethods => - [...super.instanceMethods, ...inheritedMethods]; - - @override - bool get publicInheritedInstanceMethods => - instanceMethods.every((f) => f.isInherited); - - @override - Iterable get instanceOperators => - [...super.instanceOperators, ...inheritedOperators]; - - @override - bool get publicInheritedInstanceOperators => - publicInstanceOperators.every((f) => f.isInherited); + /// Class modifiers from the Dart feature specification. + /// + /// These apply to or have some meaning for [Class]es and [Mixin]s. + late String modifiers = [ + if (isSealed) 'sealed' else if (isAbstract) 'abstract', + if (isBase) + 'base' + else if (isInterface) + 'interface' + else if (isFinal) + 'final', + if (isMixinClass) 'mixin', + ].join(' '); late final List _allModelElements = [ ...super.allModelElements, ...typeParameters, ]; - @override - List get allModelElements => _allModelElements; - - /// The [InheritingContainer] with the library in which [element] is defined. - InheritingContainer get definingContainer => - modelBuilder.from(element, definingLibrary) as InheritingContainer; - - @override - Library get enclosingElement => library; - - String get fullkind => kind; - - @override - bool get hasModifiers => - hasAnnotations || - hasPublicSuperChainReversed || - hasPotentiallyApplicableExtensions; - - bool get hasPublicSuperChainReversed => publicSuperChainReversed.isNotEmpty; - late final Iterable inheritedMethods = () { var methodNames = declaredMethods.map((m) => m.element.name).toSet(); var inheritedMethodElements = _inheritedElements @@ -251,12 +117,6 @@ abstract class InheritingContainer extends Container modelBuilder.from(e, library, enclosingContainer: this) as Method, ]; }(); - - Iterable get publicInheritedMethods => - model_utils.filterNonPublic(inheritedMethods); - - bool get hasPublicInheritedMethods => publicInheritedMethods.isNotEmpty; - late final List inheritedOperators = () { var operatorNames = declaredOperators.map((o) => o.element.name).toSet(); var inheritedOperatorElements = _inheritedElements @@ -269,62 +129,11 @@ abstract class InheritingContainer extends Container modelBuilder.from(e, library, enclosingContainer: this) as Operator, ]; }(); - - Iterable get inheritedFields => allFields.where((f) => f.isInherited); - - Iterable get publicInterfaces => const []; - - Iterable 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); - @override late final DefinedElementType modelType = modelBuilder.typeFrom(element.thisType, library) as DefinedElementType; - - /// 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 get inheritanceChain; - - List get superChain { - var typeChain = []; - 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; - } - late final List publicSuperChain = model_utils.filterNonPublic(superChain).toList(growable: false); - - Iterable get publicSuperChainReversed => - publicSuperChain.reversed; - late final List _inheritedElements = () { if (element is ClassElement && (element as ClassElement).isDartCoreObject) { return const []; @@ -426,9 +235,138 @@ abstract class InheritingContainer extends Container return fields; }(); + @override + late final Iterable declaredMethods = + element.methods.map((e) => modelBuilder.from(e, library) as Method); + + @override + late final List 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 get allModelElements => _allModelElements; + + @override + Iterable get constantFields => allFields.where((f) => f.isConst); + @override Iterable 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 { + if (modifiers.isNotEmpty) { + return '$modifiers $kind'; + } + return kind; + } + + @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 get inheritanceChain; + + Iterable get inheritedFields => allFields.where((f) => f.isInherited); + + @override + Iterable get instanceFields => allFields.where((f) => !f.isStatic); + + @override + Iterable get instanceMethods => + [...super.instanceMethods, ...inheritedMethods]; + + @override + Iterable 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 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 get publicInheritedMethods => + model_utils.filterNonPublic(inheritedMethods); + + Iterable get publicInterfaces => const []; + + Iterable get publicSuperChainReversed => + publicSuperChain.reversed; + + List get superChain { + var typeChain = []; + 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 @@ -497,39 +435,129 @@ abstract class InheritingContainer extends Container } } - @override - late final Iterable declaredMethods = - element.methods.map((e) => modelBuilder.from(e, library) as Method); + /// Returns true if [other] is a parent class for this class. + bool _isInheritingFrom(InheritingContainer? other) => superChain + .map((et) => et.modelElement as InheritingContainer) + .contains(other); +} - @override - late final List typeParameters = element.typeParameters - .map((typeParameter) => modelBuilder.from( - typeParameter, - modelBuilder.fromElement(typeParameter.enclosingElement!.library!) - as Library) as TypeParameter) +/// Add the ability to support mixed-in types to an [InheritingContainer]. +mixin MixedInTypes on InheritingContainer { + late final List mixedInTypes = element.mixins + .map((f) => modelBuilder.typeFrom(f, library) as DefinedElementType) .toList(growable: false); @override - Iterable get instanceFields => allFields.where((f) => !f.isStatic); + bool get hasModifiers => super.hasModifiers || hasPublicMixedInTypes; + + bool get hasPublicMixedInTypes => publicMixedInTypes.isNotEmpty; + + Iterable 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 directInterfaces = [ + for (var interface in element.interfaces) + modelBuilder.typeFrom(interface, library) as DefinedElementType + ]; + + late final List publicImplementorsSorted = + publicImplementors.toList(growable: false)..sort(byName); @override - bool get publicInheritedInstanceFields => - publicInstanceFields.every((f) => f.isInherited); + bool get hasModifiers => + super.hasModifiers || hasPublicInterfaces || hasPublicImplementors; + + bool get hasPublicImplementors => publicImplementors.isNotEmpty; + + bool get hasPublicInterfaces => publicInterfaces.isNotEmpty; + + /// Interfaces directly implemented by this container. + List 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 get publicImplementors { + var result = {}; + var seen = {}; + + // 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 get constantFields => allFields.where((f) => f.isConst); + Iterable 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 DefinedElementTypeIterableExtensions on Iterable { - /// Returns the `ModelElement` for each element. - Iterable get modelElements => - map((e) => e.modelElement as InheritingContainer); +extension on InterfaceElement { + bool get isDartCoreObject => name == 'Object' && library.name == 'dart.core'; +} +extension DefinedElementTypeIterableExtensions on Iterable { /// Expands the `ModelElement` for each element to its inheritance chain. Iterable get expandInheritanceChain => expand((e) => (e.modelElement as InheritingContainer).inheritanceChain); -} -extension on InterfaceElement { - bool get isDartCoreObject => name == 'Object' && library.name == 'dart.core'; + /// Returns the `ModelElement` for each element. + Iterable get modelElements => + map((e) => e.modelElement as InheritingContainer); } diff --git a/lib/src/model/mixin.dart b/lib/src/model/mixin.dart index 324c119cb3..3332ce0225 100644 --- a/lib/src/model/mixin.dart +++ b/lib/src/model/mixin.dart @@ -15,8 +15,6 @@ class Mixin extends InheritingContainer with TypeImplementing { @override final MixinElement element; - Mixin(this.element, super.library, super.packageGraph); - late final List superclassConstraints = [ ...element.superclassConstraints .map((InterfaceType i) => @@ -25,21 +23,6 @@ class Mixin extends InheritingContainer with TypeImplementing { t.modelElement != packageGraph.specialClasses[SpecialClass.object]) ]; - bool get hasPublicSuperclassConstraints => - publicSuperclassConstraints.isNotEmpty; - - Iterable get publicSuperclassConstraints => - model_utils.filterNonPublic(superclassConstraints); - - @override - bool get hasModifiers => super.hasModifiers || hasPublicSuperclassConstraints; - - @override - String get fileName => '$name-mixin.$fileType'; - - @override - String get kind => 'mixin'; - @override late final List inheritanceChain = [ this, @@ -55,11 +38,48 @@ class Mixin extends InheritingContainer with TypeImplementing { ...interfaces.expandInheritanceChain, ]; + Mixin(this.element, super.library, super.packageGraph); + @override @visibleForOverriding Iterable> get extraReferenceChildren => const []; + @override + String get fileName => '$name-mixin.$fileType'; + + @override + bool get hasModifiers => super.hasModifiers || hasPublicSuperclassConstraints; + + bool get hasPublicSuperclassConstraints => + publicSuperclassConstraints.isNotEmpty; + + @override + bool get isAbstract => false; + + @override + bool get isBase => element.isBase; + + @override + bool get isFinal => element.isFinal; + + @override + bool get isInterface => element.isInterface; + + @override + + /// Mixins are not mixin classes by definition. + bool get isMixinClass => false; + + @override + bool get isSealed => element.isSealed; + + @override + String get kind => 'mixin'; + + Iterable get publicSuperclassConstraints => + model_utils.filterNonPublic(superclassConstraints); + @override String get relationshipsClass => 'mixin-relationships'; } diff --git a/test/class_modifiers_test.dart b/test/class_modifiers_test.dart new file mode 100644 index 0000000000..d1a18f1deb --- /dev/null +++ b/test/class_modifiers_test.dart @@ -0,0 +1,91 @@ +// Copyright (c) 2023, 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:test/test.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import 'dartdoc_test_base.dart'; +import 'src/utils.dart'; + +void main() { + defineReflectiveSuite(() { + if (classModifiersAllowed) { + defineReflectiveTests(ClassModifiersTest); + } + }); +} + +@reflectiveTest +class ClassModifiersTest extends DartdocTestBase { + @override + String get libraryName => 'class_modifiers'; + + @override + String get sdkConstraint => '>=3.0.0-0.0-dev <4.0.0'; + + @override + List get experiments => ['class-modifiers', 'sealed-class']; + + /// From the table in the class modifiers feature specification. + void test_tableOfModifiers() async { + var library = await bootPackageWithLibrary(''' +class A {} +base class B {} +interface class C {} +final class D {} +sealed class E {} +abstract class F {} +abstract base class G {} +abstract interface class H {} +abstract final class I {} +mixin class J {} +base mixin class K {} +abstract mixin class L {} +abstract base mixin class M {} +mixin N {} +base mixin O {} +interface mixin P {} +final mixin Q {} +sealed mixin R {} +'''); + // This almost seems worth a map and loop, but leaving expanded for now for + // test clarity. + var Aclass = library.classes.named('A'); + var Bclass = library.classes.named('B'); + var Cclass = library.classes.named('C'); + var Dclass = library.classes.named('D'); + var Eclass = library.classes.named('E'); + var Fclass = library.classes.named('F'); + var Gclass = library.classes.named('G'); + var Hclass = library.classes.named('H'); + var Iclass = library.classes.named('I'); + var Jclass = library.classes.named('J'); + var Kclass = library.classes.named('K'); + var Lclass = library.classes.named('L'); + var Mclass = library.classes.named('M'); + var Nmixin = library.mixins.named('N'); + var Omixin = library.mixins.named('O'); + var Pmixin = library.mixins.named('P'); + var Qmixin = library.mixins.named('Q'); + var Rmixin = library.mixins.named('R'); + expect(Aclass.fullkind, equals('class')); + expect(Bclass.fullkind, equals('base class')); + expect(Cclass.fullkind, equals('interface class')); + expect(Dclass.fullkind, equals('final class')); + expect(Eclass.fullkind, equals('sealed class')); + expect(Fclass.fullkind, equals('abstract class')); + expect(Gclass.fullkind, equals('abstract base class')); + expect(Hclass.fullkind, equals('abstract interface class')); + expect(Iclass.fullkind, equals('abstract final class')); + expect(Jclass.fullkind, equals('mixin class')); + expect(Kclass.fullkind, equals('base mixin class')); + expect(Lclass.fullkind, equals('abstract mixin class')); + expect(Mclass.fullkind, equals('abstract base mixin class')); + expect(Nmixin.fullkind, equals('mixin')); + expect(Omixin.fullkind, equals('base mixin')); + expect(Pmixin.fullkind, equals('interface mixin')); + expect(Qmixin.fullkind, equals('final mixin')); + expect(Rmixin.fullkind, equals('sealed mixin')); + } +} diff --git a/test/dartdoc_test_base.dart b/test/dartdoc_test_base.dart index 8ac52d7c37..ad4dca0909 100644 --- a/test/dartdoc_test_base.dart +++ b/test/dartdoc_test_base.dart @@ -44,7 +44,7 @@ abstract class DartdocTestBase { if (experiments.isNotEmpty) { analysisOptions = ''' analyzer: - enable-experiment:${experiments.map((experiment) => '\n - $experiment')} + enable-experiment:${experiments.map((experiment) => '\n - $experiment').join('')} '''; } packagePath = await d.createPackage( diff --git a/test/src/test_descriptor_utils.dart b/test/src/test_descriptor_utils.dart index a8dceb8080..d411c75ee4 100644 --- a/test/src/test_descriptor_utils.dart +++ b/test/src/test_descriptor_utils.dart @@ -35,8 +35,9 @@ Future createPackage( final parsedYaml = yaml.loadYaml(pubspec) as Map; final packageName = parsedYaml['name']; final versionConstraint = (parsedYaml['environment'] as Map)['sdk'] as String; - final languageVersion = - RegExp(r'>=(\S*)\.0(-0)? ').firstMatch(versionConstraint)!.group(1); + final languageVersion = RegExp(r'>=(\S*)\.0(-0)?(-0.0-dev)? ') + .firstMatch(versionConstraint)! + .group(1); final packagesInfo = StringBuffer('''{ "name": "$packageName", "rootUri": "../", diff --git a/test/src/utils.dart b/test/src/utils.dart index 88dbe849c3..fe2b26841e 100644 --- a/test/src/utils.dart +++ b/test/src/utils.dart @@ -349,6 +349,13 @@ bool get recordsAllowed => VersionRange(min: Version.parse('2.19.0-0'), includeMin: true) .allows(platformVersion); +/// We can not use [ExperimentalFeature.releaseVersion] or even +/// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null` +/// even when partial analyzer implementations are available. +bool get classModifiersAllowed => + VersionRange(min: Version.parse('3.0.0-0.0-dev'), includeMin: true) + .allows(platformVersion); + /// We can not use [ExperimentalFeature.releaseVersion] or even /// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null` /// even when partial analyzer implementations are available.