Skip to content

Commit c02f0b5

Browse files
authored
Add chips to class pages for class modifiers (#3401)
* Add chips to class pages * rebuild * fullkind no longer shows abstract, that is intentional
1 parent 7a7da5c commit c02f0b5

8 files changed

+116
-57
lines changed

lib/resources/styles.css

+2-1
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ h1 .category {
550550
vertical-align: middle;
551551
}
552552

553-
/* The badge under a declaration for things like "const", "read-only", etc. and for the badges inline like Null safety*/
553+
/* The badge under a declaration for things like "const", "read-only", etc. and for the badges inline like sealed or interface */
554554
/* See https://github.com/dart-lang/dartdoc/blob/main/lib/src/model/feature.dart */
555555
.feature {
556556
display: inline-block;
@@ -570,6 +570,7 @@ a.feature:hover {
570570

571571
h1 .feature {
572572
vertical-align: middle;
573+
margin: 0 -2px 0 0;
573574
}
574575

575576
.source-link {

lib/src/generator/templates.runtime_renderers.dart

+13
Original file line numberDiff line numberDiff line change
@@ -6867,6 +6867,19 @@ class _Renderer_InheritingContainer extends RendererBase<InheritingContainer> {
68676867
parent: r);
68686868
},
68696869
),
6870+
'displayedLanguageFeatures': Property(
6871+
getValue: (CT_ c) => c.displayedLanguageFeatures,
6872+
renderVariable: (CT_ c, Property<CT_> self,
6873+
List<String> remainingNames) =>
6874+
self.renderSimpleVariable(
6875+
c, remainingNames, 'List<LanguageFeature>'),
6876+
renderIterable: (CT_ c, RendererBase<CT_> r,
6877+
List<MustachioNode> ast, StringSink sink) {
6878+
return c.displayedLanguageFeatures.map((e) =>
6879+
_render_LanguageFeature(e, ast, r.template, sink,
6880+
parent: r));
6881+
},
6882+
),
68706883
'element': Property(
68716884
getValue: (CT_ c) => c.element,
68726885
renderVariable: (CT_ c, Property<CT_> self,

lib/src/model/container_modifiers.dart

+10-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'package:collection/collection.dart' show IterableExtension;
5+
import 'package:dartdoc/src/model/language_feature.dart';
6+
import 'package:dartdoc/src/render/language_feature_renderer.dart';
67

78
/// Represents a single modifier applicable to containers.
89
class ContainerModifier implements Comparable<ContainerModifier> {
@@ -15,6 +16,7 @@ class ContainerModifier implements Comparable<ContainerModifier> {
1516

1617
/// The display order of this modifier.
1718
final int order;
19+
1820
const ContainerModifier._(
1921
this.name, {
2022
required this.order,
@@ -41,10 +43,11 @@ class ContainerModifier implements Comparable<ContainerModifier> {
4143
static const ContainerModifier mixin = ContainerModifier._('mixin', order: 4);
4244
}
4345

44-
extension ContainerModifiers on Iterable<ContainerModifier> {
45-
/// Returns a string suitable for prefixing the class name in Dartdoc
46-
/// title bars based on given modifiers.
47-
String modifiersAsFullKindPrefix() => sorted((a, b) => a.compareTo(b))
48-
.where((m) => !m.hideIfPresent.any(contains))
49-
.join(' ');
46+
extension BuildLanguageFeatureSet on Iterable<ContainerModifier> {
47+
/// Transforms [ContainerModifiers] into a series of [LanguageFeature] objects
48+
/// suitable for rendering as chips. Assumes iterable is sorted.
49+
Iterable<LanguageFeature> asLanguageFeatureSet(
50+
LanguageFeatureRenderer languageFeatureRenderer) =>
51+
where((m) => !m.hideIfPresent.any(contains))
52+
.map((m) => LanguageFeature(m.name, languageFeatureRenderer));
5053
}

lib/src/model/inheriting_container.dart

+11-8
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:dartdoc/src/element_type.dart';
99
import 'package:dartdoc/src/model/comment_referable.dart';
1010
import 'package:dartdoc/src/model/container_modifiers.dart';
1111
import 'package:dartdoc/src/model/extension_target.dart';
12+
import 'package:dartdoc/src/model/language_feature.dart';
1213
import 'package:dartdoc/src/model/model.dart';
1314
import 'package:dartdoc/src/model_utils.dart' as model_utils;
1415
import 'package:meta/meta.dart';
@@ -87,14 +88,21 @@ abstract class InheritingContainer extends Container
8788
/// Class modifiers from the Dart feature specification.
8889
///
8990
/// These apply to or have some meaning for [Class]es and [Mixin]s.
90-
late List<ContainerModifier> containerModifiers = [
91+
late final List<ContainerModifier> containerModifiers = [
9192
if (isAbstract) ContainerModifier.abstract,
9293
if (isSealed) ContainerModifier.sealed,
9394
if (isBase) ContainerModifier.base,
9495
if (isInterface) ContainerModifier.interface,
9596
if (isFinal) ContainerModifier.finalModifier,
9697
if (isMixinClass) ContainerModifier.mixin,
97-
];
98+
]..sort();
99+
100+
@override
101+
late final List<LanguageFeature> displayedLanguageFeatures =
102+
containerModifiers
103+
.asLanguageFeatureSet(
104+
packageGraph.rendererFactory.languageFeatureRenderer)
105+
.toList();
98106

99107
late final List<ModelElement> _allModelElements = [
100108
...super.allModelElements,
@@ -267,12 +275,7 @@ abstract class InheritingContainer extends Container
267275
@override
268276
Library get enclosingElement => library;
269277

270-
String get fullkind {
271-
if (containerModifiers.isNotEmpty) {
272-
return '${containerModifiers.modifiersAsFullKindPrefix()} $kind';
273-
}
274-
return kind;
275-
}
278+
String get fullkind => kind;
276279

277280
@override
278281
bool get hasModifiers =>

lib/src/model/language_feature.dart

+27-6
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,33 @@
44

55
import 'package:dartdoc/src/render/language_feature_renderer.dart';
66

7-
const Map<String, String> _featureDescriptions = {};
8-
9-
const Map<String, String> _featureUrls = {};
10-
11-
/// An abstraction for a language feature; used to render tags to notify
12-
/// the user that the documentation should be specially interpreted.
7+
const Map<String, String> _featureDescriptions = {
8+
'sealed': 'All direct subtypes must be defined in the same library.',
9+
'abstract': 'This type can not be directly constructed.',
10+
'base': 'This type can only be extended (not implemented or mixed in).',
11+
'interface': 'This type can only be implemented (not extended or mixed in).',
12+
'final': 'This type can neither be extended, implemented, nor mixed in.',
13+
'mixin': 'This type can be used as a class and a mixin.',
14+
};
15+
16+
const Map<String, String> _featureUrls = {
17+
// TODO(jcollins-g): link to dart.dev for all links once documentation is
18+
// available.
19+
'sealed':
20+
'https://github.com/dart-lang/language/blob/main/accepted/future-releases/sealed-types/feature-specification.md#sealed-types',
21+
'abstract': 'https://dart.dev/language/classes#abstract-classes',
22+
'base':
23+
'https://github.com/dart-lang/language/blob/main/accepted/future-releases/class-modifiers/feature-specification.md#class-modifiers',
24+
'interface':
25+
'https://github.com/dart-lang/language/blob/main/accepted/future-releases/class-modifiers/feature-specification.md#class-modifiers',
26+
'final':
27+
'https://github.com/dart-lang/language/blob/main/accepted/future-releases/class-modifiers/feature-specification.md#class-modifiers',
28+
'mixin':
29+
'https://github.com/dart-lang/language/blob/main/accepted/future-releases/class-modifiers/feature-specification.md#class-modifiers',
30+
};
31+
32+
/// An abstraction for a language feature; used to render tags ('chips') to
33+
/// notify the user that the documentation should be specially interpreted.
1334
class LanguageFeature {
1435
/// The description of this language feature.
1536
String? get featureDescription => _featureDescriptions[name];

test/class_modifiers_test.dart

+32-24
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:dartdoc/src/model/model.dart';
56
import 'package:test/test.dart';
67
import 'package:test_reflective_loader/test_reflective_loader.dart';
78

89
import 'dartdoc_test_base.dart';
910
import 'src/utils.dart';
1011

12+
extension on InheritingContainer {
13+
String languageFeatureChips() =>
14+
displayedLanguageFeatures.map((l) => l.name).join(' ');
15+
}
16+
1117
void main() {
1218
defineReflectiveSuite(() {
1319
if (classModifiersAllowed) {
@@ -63,21 +69,21 @@ base mixin O {}
6369
var Mclass = library.classes.named('M');
6470
var Nmixin = library.mixins.named('N');
6571
var Omixin = library.mixins.named('O');
66-
expect(Aclass.fullkind, equals('class'));
67-
expect(Bclass.fullkind, equals('base class'));
68-
expect(Cclass.fullkind, equals('interface class'));
69-
expect(Dclass.fullkind, equals('final class'));
70-
expect(Eclass.fullkind, equals('sealed class'));
71-
expect(Fclass.fullkind, equals('abstract class'));
72-
expect(Gclass.fullkind, equals('abstract base class'));
73-
expect(Hclass.fullkind, equals('abstract interface class'));
74-
expect(Iclass.fullkind, equals('abstract final class'));
75-
expect(Jclass.fullkind, equals('mixin class'));
76-
expect(Kclass.fullkind, equals('base mixin class'));
77-
expect(Lclass.fullkind, equals('abstract mixin class'));
78-
expect(Mclass.fullkind, equals('abstract base mixin class'));
79-
expect(Nmixin.fullkind, equals('mixin'));
80-
expect(Omixin.fullkind, equals('base mixin'));
72+
expect(Aclass.languageFeatureChips(), equals(''));
73+
expect(Bclass.languageFeatureChips(), equals('base'));
74+
expect(Cclass.languageFeatureChips(), equals('interface'));
75+
expect(Dclass.languageFeatureChips(), equals('final'));
76+
expect(Eclass.languageFeatureChips(), equals('sealed'));
77+
expect(Fclass.languageFeatureChips(), equals('abstract'));
78+
expect(Gclass.languageFeatureChips(), equals('abstract base'));
79+
expect(Hclass.languageFeatureChips(), equals('abstract interface'));
80+
expect(Iclass.languageFeatureChips(), equals('abstract final'));
81+
expect(Jclass.languageFeatureChips(), equals('mixin'));
82+
expect(Kclass.languageFeatureChips(), equals('base mixin'));
83+
expect(Lclass.languageFeatureChips(), equals('abstract mixin'));
84+
expect(Mclass.languageFeatureChips(), equals('abstract base mixin'));
85+
expect(Nmixin.languageFeatureChips(), equals(''));
86+
expect(Omixin.languageFeatureChips(), equals('base'));
8187
}
8288

8389
void test_abstractSealed() async {
@@ -86,7 +92,8 @@ abstract class A {}
8692
sealed class B extends A {}
8793
''');
8894
var Bclass = library.classes.named('B');
89-
expect(Bclass.fullkind, equals('sealed class')); // *not* sealed abstract
95+
expect(Bclass.languageFeatureChips(),
96+
equals('sealed')); // *not* sealed abstract
9097
}
9198

9299
void test_inferredModifiers() async {
@@ -116,13 +123,14 @@ base class M extends L {}
116123
var Iclass = library.classes.named('I');
117124
var Lclass = library.classes.named('L');
118125
var Mclass = library.classes.named('M');
119-
expect(Bclass.fullkind, equals('sealed class')); // *not* sealed base
120-
expect(Cclass.fullkind, equals('base class'));
121-
expect(Eclass.fullkind, equals('sealed class'));
122-
expect(Fclass.fullkind, equals('interface class'));
123-
expect(Hclass.fullkind, equals('sealed class'));
124-
expect(Iclass.fullkind, equals('final class'));
125-
expect(Lclass.fullkind, equals('sealed class'));
126-
expect(Mclass.fullkind, equals('base class'));
126+
expect(
127+
Bclass.languageFeatureChips(), equals('sealed')); // *not* sealed base
128+
expect(Cclass.languageFeatureChips(), equals('base'));
129+
expect(Eclass.languageFeatureChips(), equals('sealed'));
130+
expect(Fclass.languageFeatureChips(), equals('interface'));
131+
expect(Hclass.languageFeatureChips(), equals('sealed'));
132+
expect(Iclass.languageFeatureChips(), equals('final'));
133+
expect(Lclass.languageFeatureChips(), equals('sealed'));
134+
expect(Mclass.languageFeatureChips(), equals('base'));
127135
}
128136
}

test/container_modifiers_test.dart

+21-7
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,41 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:dartdoc/src/model/container_modifiers.dart';
6+
import 'package:dartdoc/src/model/language_feature.dart';
7+
import 'package:dartdoc/src/render/language_feature_renderer.dart';
68
import 'package:test/test.dart';
79

10+
class TestChipRenderer extends LanguageFeatureRenderer {
11+
@override
12+
String renderLanguageFeatureLabel(LanguageFeature l) => l.name;
13+
}
14+
15+
extension TestChipsRenderer on Iterable<LanguageFeature> {
16+
String asRenderedString() => map((l) => l.featureLabel).join(' ');
17+
}
18+
819
void main() {
920
group('fullKind string tests', () {
1021
test('basic', () {
11-
var l = {
22+
var l = [
1223
ContainerModifier.base,
1324
ContainerModifier.interface,
1425
ContainerModifier.abstract
15-
};
16-
expect(l.modifiersAsFullKindPrefix(), equals('abstract base interface'));
26+
]..sort();
27+
expect(l.asLanguageFeatureSet(TestChipRenderer()).asRenderedString(),
28+
equals('abstract base interface'));
1729
});
1830

1931
test('hide abstract on sealed', () {
20-
var l = {ContainerModifier.abstract, ContainerModifier.sealed};
21-
expect(l.modifiersAsFullKindPrefix(), equals('sealed'));
32+
var l = [ContainerModifier.abstract, ContainerModifier.sealed]..sort();
33+
expect(l.asLanguageFeatureSet(TestChipRenderer()).asRenderedString(),
34+
equals('sealed'));
2235
});
2336

2437
test('empty', () {
25-
var l = <ContainerModifier>{};
26-
expect(l.modifiersAsFullKindPrefix(), equals(''));
38+
var l = <ContainerModifier>[];
39+
expect(l.asLanguageFeatureSet(TestChipRenderer()).asRenderedString(),
40+
equals(''));
2741
});
2842
});
2943
}

test/end2end/model_test.dart

-4
Original file line numberDiff line numberDiff line change
@@ -1991,10 +1991,6 @@ void main() {
19911991
expect(interfaces[1].name, 'E');
19921992
});
19931993

1994-
test('class title has abstract keyword', () {
1995-
expect(Cat.fullkind, 'abstract class');
1996-
});
1997-
19981994
test('class title has no abstract keyword', () {
19991995
expect(Dog.fullkind, 'class');
20001996
});

0 commit comments

Comments
 (0)