From c38eed2ec6551a32121bbe571b97f4c3f0d2c1eb Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Thu, 27 Apr 2023 09:23:24 -0700 Subject: [PATCH 1/3] Add chips to class pages --- lib/resources/styles.css | 3 +- lib/src/model/container_modifiers.dart | 17 ++++---- lib/src/model/inheriting_container.dart | 19 +++++---- lib/src/model/language_feature.dart | 33 ++++++++++++--- test/class_modifiers_test.dart | 56 ++++++++++++++----------- test/container_modifiers_test.dart | 28 +++++++++---- 6 files changed, 103 insertions(+), 53 deletions(-) diff --git a/lib/resources/styles.css b/lib/resources/styles.css index 33c0244cdd..09b85b5188 100644 --- a/lib/resources/styles.css +++ b/lib/resources/styles.css @@ -550,7 +550,7 @@ h1 .category { vertical-align: middle; } -/* The badge under a declaration for things like "const", "read-only", etc. and for the badges inline like Null safety*/ +/* The badge under a declaration for things like "const", "read-only", etc. and for the badges inline like sealed or interface */ /* See https://github.com/dart-lang/dartdoc/blob/main/lib/src/model/feature.dart */ .feature { display: inline-block; @@ -570,6 +570,7 @@ a.feature:hover { h1 .feature { vertical-align: middle; + margin: 0 -2px 0 0; } .source-link { diff --git a/lib/src/model/container_modifiers.dart b/lib/src/model/container_modifiers.dart index 94ebf08127..6472054e55 100644 --- a/lib/src/model/container_modifiers.dart +++ b/lib/src/model/container_modifiers.dart @@ -2,7 +2,8 @@ // 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:collection/collection.dart' show IterableExtension; +import 'package:dartdoc/src/model/language_feature.dart'; +import 'package:dartdoc/src/render/language_feature_renderer.dart'; /// Represents a single modifier applicable to containers. class ContainerModifier implements Comparable { @@ -15,6 +16,7 @@ class ContainerModifier implements Comparable { /// The display order of this modifier. final int order; + const ContainerModifier._( this.name, { required this.order, @@ -41,10 +43,11 @@ class ContainerModifier implements Comparable { static const ContainerModifier mixin = ContainerModifier._('mixin', order: 4); } -extension ContainerModifiers on Iterable { - /// Returns a string suitable for prefixing the class name in Dartdoc - /// title bars based on given modifiers. - String modifiersAsFullKindPrefix() => sorted((a, b) => a.compareTo(b)) - .where((m) => !m.hideIfPresent.any(contains)) - .join(' '); +extension BuildLanguageFeatureSet on Iterable { + /// Transforms [ContainerModifiers] into a series of [LanguageFeature] objects + /// suitable for rendering as chips. Assumes iterable is sorted. + Iterable asLanguageFeatureSet( + LanguageFeatureRenderer languageFeatureRenderer) => + where((m) => !m.hideIfPresent.any(contains)) + .map((m) => LanguageFeature(m.name, languageFeatureRenderer)); } diff --git a/lib/src/model/inheriting_container.dart b/lib/src/model/inheriting_container.dart index 756d6524f2..9b049068eb 100644 --- a/lib/src/model/inheriting_container.dart +++ b/lib/src/model/inheriting_container.dart @@ -9,6 +9,7 @@ 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'; @@ -87,14 +88,21 @@ abstract class InheritingContainer extends Container /// Class modifiers from the Dart feature specification. /// /// These apply to or have some meaning for [Class]es and [Mixin]s. - late List containerModifiers = [ + late final List 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 displayedLanguageFeatures = + containerModifiers + .asLanguageFeatureSet( + packageGraph.rendererFactory.languageFeatureRenderer) + .toList(); late final List _allModelElements = [ ...super.allModelElements, @@ -267,12 +275,7 @@ abstract class InheritingContainer extends Container @override Library get enclosingElement => library; - String get fullkind { - if (containerModifiers.isNotEmpty) { - return '${containerModifiers.modifiersAsFullKindPrefix()} $kind'; - } - return kind; - } + String get fullkind => kind; @override bool get hasModifiers => diff --git a/lib/src/model/language_feature.dart b/lib/src/model/language_feature.dart index 0813f2ac0e..41df7e5013 100644 --- a/lib/src/model/language_feature.dart +++ b/lib/src/model/language_feature.dart @@ -4,12 +4,33 @@ import 'package:dartdoc/src/render/language_feature_renderer.dart'; -const Map _featureDescriptions = {}; - -const Map _featureUrls = {}; - -/// An abstraction for a language feature; used to render tags to notify -/// the user that the documentation should be specially interpreted. +const Map _featureDescriptions = { + 'sealed': 'All direct subtypes must be defined in the same library.', + 'abstract': 'This type can not be directly constructed.', + 'base': 'This type can only be extended (not implemented or mixed in).', + 'interface': 'This type can only be implemented (not extended or mixed in).', + 'final': 'This type can neither be extended, implemented, nor mixed in.', + 'mixin': 'This type can be used as a class and a mixin.', +}; + +const Map _featureUrls = { + // TODO(jcollins-g): link to dart.dev for all links once documentation is + // available. + 'sealed': + 'https://github.com/dart-lang/language/blob/main/accepted/future-releases/sealed-types/feature-specification.md#sealed-types', + 'abstract': 'https://dart.dev/language/classes#abstract-classes', + 'base': + 'https://github.com/dart-lang/language/blob/main/accepted/future-releases/class-modifiers/feature-specification.md#class-modifiers', + 'interface': + 'https://github.com/dart-lang/language/blob/main/accepted/future-releases/class-modifiers/feature-specification.md#class-modifiers', + 'final': + 'https://github.com/dart-lang/language/blob/main/accepted/future-releases/class-modifiers/feature-specification.md#class-modifiers', + 'mixin': + 'https://github.com/dart-lang/language/blob/main/accepted/future-releases/class-modifiers/feature-specification.md#class-modifiers', +}; + +/// An abstraction for a language feature; used to render tags ('chips') to +/// notify the user that the documentation should be specially interpreted. class LanguageFeature { /// The description of this language feature. String? get featureDescription => _featureDescriptions[name]; diff --git a/test/class_modifiers_test.dart b/test/class_modifiers_test.dart index e2c2dabf43..9d8bd8bc32 100644 --- a/test/class_modifiers_test.dart +++ b/test/class_modifiers_test.dart @@ -2,12 +2,18 @@ // 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:dartdoc/src/model/model.dart'; import 'package:test/test.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'dartdoc_test_base.dart'; import 'src/utils.dart'; +extension on InheritingContainer { + String languageFeatureChips() => + displayedLanguageFeatures.map((l) => l.name).join(' '); +} + void main() { defineReflectiveSuite(() { if (classModifiersAllowed) { @@ -63,21 +69,21 @@ base mixin O {} var Mclass = library.classes.named('M'); var Nmixin = library.mixins.named('N'); var Omixin = library.mixins.named('O'); - 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(Aclass.languageFeatureChips(), equals('')); + expect(Bclass.languageFeatureChips(), equals('base')); + expect(Cclass.languageFeatureChips(), equals('interface')); + expect(Dclass.languageFeatureChips(), equals('final')); + expect(Eclass.languageFeatureChips(), equals('sealed')); + expect(Fclass.languageFeatureChips(), equals('abstract')); + expect(Gclass.languageFeatureChips(), equals('abstract base')); + expect(Hclass.languageFeatureChips(), equals('abstract interface')); + expect(Iclass.languageFeatureChips(), equals('abstract final')); + expect(Jclass.languageFeatureChips(), equals('mixin')); + expect(Kclass.languageFeatureChips(), equals('base mixin')); + expect(Lclass.languageFeatureChips(), equals('abstract mixin')); + expect(Mclass.languageFeatureChips(), equals('abstract base mixin')); + expect(Nmixin.languageFeatureChips(), equals('')); + expect(Omixin.languageFeatureChips(), equals('base')); } void test_abstractSealed() async { @@ -86,7 +92,8 @@ abstract class A {} sealed class B extends A {} '''); var Bclass = library.classes.named('B'); - expect(Bclass.fullkind, equals('sealed class')); // *not* sealed abstract + expect(Bclass.languageFeatureChips(), + equals('sealed')); // *not* sealed abstract } void test_inferredModifiers() async { @@ -116,13 +123,14 @@ base class M extends L {} var Iclass = library.classes.named('I'); var Lclass = library.classes.named('L'); var Mclass = library.classes.named('M'); - expect(Bclass.fullkind, equals('sealed class')); // *not* sealed base - expect(Cclass.fullkind, equals('base class')); - expect(Eclass.fullkind, equals('sealed class')); - expect(Fclass.fullkind, equals('interface class')); - expect(Hclass.fullkind, equals('sealed class')); - expect(Iclass.fullkind, equals('final class')); - expect(Lclass.fullkind, equals('sealed class')); - expect(Mclass.fullkind, equals('base class')); + expect( + Bclass.languageFeatureChips(), equals('sealed')); // *not* sealed base + expect(Cclass.languageFeatureChips(), equals('base')); + expect(Eclass.languageFeatureChips(), equals('sealed')); + expect(Fclass.languageFeatureChips(), equals('interface')); + expect(Hclass.languageFeatureChips(), equals('sealed')); + expect(Iclass.languageFeatureChips(), equals('final')); + expect(Lclass.languageFeatureChips(), equals('sealed')); + expect(Mclass.languageFeatureChips(), equals('base')); } } diff --git a/test/container_modifiers_test.dart b/test/container_modifiers_test.dart index b1b77f9dd0..a952a962e9 100644 --- a/test/container_modifiers_test.dart +++ b/test/container_modifiers_test.dart @@ -3,27 +3,41 @@ // BSD-style license that can be found in the LICENSE file. import 'package:dartdoc/src/model/container_modifiers.dart'; +import 'package:dartdoc/src/model/language_feature.dart'; +import 'package:dartdoc/src/render/language_feature_renderer.dart'; import 'package:test/test.dart'; +class TestChipRenderer extends LanguageFeatureRenderer { + @override + String renderLanguageFeatureLabel(LanguageFeature l) => l.name; +} + +extension TestChipsRenderer on Iterable { + String asRenderedString() => map((l) => l.featureLabel).join(' '); +} + void main() { group('fullKind string tests', () { test('basic', () { - var l = { + var l = [ ContainerModifier.base, ContainerModifier.interface, ContainerModifier.abstract - }; - expect(l.modifiersAsFullKindPrefix(), equals('abstract base interface')); + ]..sort(); + expect(l.asLanguageFeatureSet(TestChipRenderer()).asRenderedString(), + equals('abstract base interface')); }); test('hide abstract on sealed', () { - var l = {ContainerModifier.abstract, ContainerModifier.sealed}; - expect(l.modifiersAsFullKindPrefix(), equals('sealed')); + var l = [ContainerModifier.abstract, ContainerModifier.sealed]..sort(); + expect(l.asLanguageFeatureSet(TestChipRenderer()).asRenderedString(), + equals('sealed')); }); test('empty', () { - var l = {}; - expect(l.modifiersAsFullKindPrefix(), equals('')); + var l = []; + expect(l.asLanguageFeatureSet(TestChipRenderer()).asRenderedString(), + equals('')); }); }); } From 14935623471096aa271da0f3fd6919eeb228967f Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Thu, 27 Apr 2023 09:35:00 -0700 Subject: [PATCH 2/3] rebuild --- lib/src/generator/templates.runtime_renderers.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/src/generator/templates.runtime_renderers.dart b/lib/src/generator/templates.runtime_renderers.dart index 8da62b49db..cf3fa01b08 100644 --- a/lib/src/generator/templates.runtime_renderers.dart +++ b/lib/src/generator/templates.runtime_renderers.dart @@ -6867,6 +6867,19 @@ class _Renderer_InheritingContainer extends RendererBase { parent: r); }, ), + 'displayedLanguageFeatures': Property( + getValue: (CT_ c) => c.displayedLanguageFeatures, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable( + c, remainingNames, 'List'), + renderIterable: (CT_ c, RendererBase r, + List ast, StringSink sink) { + return c.displayedLanguageFeatures.map((e) => + _render_LanguageFeature(e, ast, r.template, sink, + parent: r)); + }, + ), 'element': Property( getValue: (CT_ c) => c.element, renderVariable: (CT_ c, Property self, From abf7af6fc64af80e0bab7dc509c7decd5320134b Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Thu, 27 Apr 2023 09:40:22 -0700 Subject: [PATCH 3/3] fullkind no longer shows abstract, that is intentional --- test/end2end/model_test.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/end2end/model_test.dart b/test/end2end/model_test.dart index 94a73c9895..2e27dd69be 100644 --- a/test/end2end/model_test.dart +++ b/test/end2end/model_test.dart @@ -1991,10 +1991,6 @@ void main() { expect(interfaces[1].name, 'E'); }); - test('class title has abstract keyword', () { - expect(Cat.fullkind, 'abstract class'); - }); - test('class title has no abstract keyword', () { expect(Dog.fullkind, 'class'); });