Skip to content

Commit 220b32a

Browse files
authored
Link const annotations to their docs (dart-lang#2313)
Link const annotations to their docs
1 parent b7da88b commit 220b32a

File tree

5 files changed

+81
-45
lines changed

5 files changed

+81
-45
lines changed

lib/src/model/field.dart

+4-5
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,14 @@ class Field extends ModelElement
105105

106106
@override
107107
List<String> get annotations {
108-
var all_annotations = <String>[];
109-
all_annotations.addAll(super.annotations);
108+
var allAnnotations = [...super.annotations];
110109

111110
if (element is PropertyInducingElement) {
112111
var pie = element as PropertyInducingElement;
113-
all_annotations.addAll(annotationsFromMetadata(pie.getter?.metadata));
114-
all_annotations.addAll(annotationsFromMetadata(pie.setter?.metadata));
112+
allAnnotations.addAll(annotationsFromMetadata(pie.getter?.metadata));
113+
allAnnotations.addAll(annotationsFromMetadata(pie.setter?.metadata));
115114
}
116-
return all_annotations.toList(growable: false);
115+
return allAnnotations;
117116
}
118117

119118
@override

lib/src/model/model_element.dart

+42-26
Original file line numberDiff line numberDiff line change
@@ -399,46 +399,63 @@ abstract class ModelElement extends Canonicalization
399399
List<String> get annotations => annotationsFromMetadata(element.metadata);
400400

401401
/// Returns linked annotations from a given metadata set, with escaping.
402+
// TODO(srawlins): Attempt to revive constructor arguments in an annotation,
403+
// akin to source_gen's Reviver, in order to link to inner components. For
404+
// example, in `@Foo(const Bar(), baz: <Baz>[Baz.one, Baz.two])`, link to
405+
// `Foo`, `Bar`, `Baz`, `Baz.one`, and `Baz.two`.
402406
List<String> annotationsFromMetadata(List<ElementAnnotation> md) {
403407
var annotationStrings = <String>[];
404408
if (md == null) return annotationStrings;
405409
for (var a in md) {
406410
var annotation = (const HtmlEscape()).convert(a.toSource());
407411
var annotationElement = a.element;
408412

409-
ClassElement annotationClassElement;
410-
if (annotationElement is ExecutableElement) {
413+
if (annotationElement is ConstructorElement) {
414+
// TODO(srawlins): I think we should actually link to the constructor,
415+
// which may have details about parameters. For example, given the
416+
// annotation `@Immutable('text')`, the constructor documents what the
417+
// parameter is, and the class only references `immutable`. It's a
418+
// lose-lose cycle of mis-direction.
411419
annotationElement =
412-
(annotationElement as ExecutableElement).returnType.element;
420+
(annotationElement as ConstructorElement).returnType.element;
421+
} else if (annotationElement is PropertyAccessorElement) {
422+
annotationElement =
423+
(annotationElement as PropertyAccessorElement).variable;
413424
}
414-
if (annotationElement is ClassElement) {
415-
annotationClassElement = annotationElement;
425+
if (annotationElement is Member) {
426+
annotationElement = (annotationElement as Member).declaration;
416427
}
428+
429+
// Some annotations are intended to be invisible (such as `@pragma`).
430+
if (!_shouldDisplayAnnotation(annotationElement)) continue;
431+
417432
var annotationModelElement =
418433
packageGraph.findCanonicalModelElementFor(annotationElement);
419-
// annotationElement can be null if the element can't be resolved.
420-
var annotationClass = packageGraph
421-
.findCanonicalModelElementFor(annotationClassElement) as Class;
422-
if (annotationClass == null &&
423-
annotationElement != null &&
424-
annotationClassElement != null) {
425-
annotationClass =
426-
ModelElement.fromElement(annotationClassElement, packageGraph)
427-
as Class;
428-
}
429-
// Some annotations are intended to be invisible (@pragma)
430-
if (annotationClass == null ||
431-
!packageGraph.invisibleAnnotations.contains(annotationClass)) {
432-
if (annotationModelElement != null) {
433-
annotation = annotation.replaceFirst(
434-
annotationModelElement.name, annotationModelElement.linkedName);
435-
}
436-
annotationStrings.add(annotation);
434+
if (annotationModelElement != null) {
435+
annotation = annotation.replaceFirst(
436+
annotationModelElement.name, annotationModelElement.linkedName);
437437
}
438+
annotationStrings.add(annotation);
438439
}
439440
return annotationStrings;
440441
}
441442

443+
bool _shouldDisplayAnnotation(Element annotationElement) {
444+
if (annotationElement is ClassElement) {
445+
var annotationClass =
446+
packageGraph.findCanonicalModelElementFor(annotationElement) as Class;
447+
if (annotationClass == null && annotationElement != null) {
448+
annotationClass =
449+
ModelElement.fromElement(annotationElement, packageGraph) as Class;
450+
}
451+
452+
return annotationClass == null ||
453+
packageGraph.isAnnotationVisible(annotationClass);
454+
}
455+
// We cannot resolve it, which does not prevent it from being displayed.
456+
return true;
457+
}
458+
442459
bool _isPublic;
443460

444461
@override
@@ -506,14 +523,13 @@ abstract class ModelElement extends Canonicalization
506523
.where((s) => s.isNotEmpty));
507524

508525
Set<String> get features {
509-
var allFeatures = <String>{};
510-
allFeatures.addAll(annotations);
526+
var allFeatures = <String>{...annotations};
511527

512528
// Replace the @override annotation with a feature that explicitly
513529
// indicates whether an override has occurred.
514530
allFeatures.remove('@override');
515531

516-
// Drop the plain "deprecated" annotation, that's indicated via
532+
// Drop the plain "deprecated" annotation; that's indicated via
517533
// strikethroughs. Custom @Deprecated() will still appear.
518534
allFeatures.remove('@deprecated');
519535
// const and static are not needed here because const/static elements get

lib/src/model/package_graph.dart

+5-7
Original file line numberDiff line numberDiff line change
@@ -641,13 +641,11 @@ class PackageGraph {
641641

642642
/// Returns the set of [Class] objects that are similar to pragma
643643
/// in that we should never count them as documentable annotations.
644-
Set<Class> get invisibleAnnotations {
645-
if (_invisibleAnnotations == null) {
646-
_invisibleAnnotations = {};
647-
_invisibleAnnotations.add(specialClasses[SpecialClass.pragma]);
648-
}
649-
return _invisibleAnnotations;
650-
}
644+
Set<Class> get invisibleAnnotations =>
645+
_invisibleAnnotations ??= {specialClasses[SpecialClass.pragma]};
646+
647+
bool isAnnotationVisible(Class class_) =>
648+
!invisibleAnnotations.contains(class_);
651649

652650
@override
653651
String toString() {

test/model_test.dart

+25-7
Original file line numberDiff line numberDiff line change
@@ -3671,7 +3671,10 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans,
36713671
fakeLibrary.functions.firstWhere((f) => f.name == 'paintImage1');
36723672
var params =
36733673
ParameterRendererHtml().renderLinkedParams(method.parameters);
3674-
expect(params, contains('@required'));
3674+
expect(
3675+
params,
3676+
contains(
3677+
'@<a href="${HTMLBASE_PLACEHOLDER}fake/required-constant.html">required</a>'));
36753678
});
36763679

36773680
test('param exported in library', () {
@@ -3685,8 +3688,9 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans,
36853688
.renderLinkedParams(methodWithTypedefParam.parameters);
36863689
expect(
36873690
params,
3688-
equals(
3689-
'<span class="parameter" id="methodWithTypedefParam-param-p"><span class="type-annotation"><a href="${HTMLBASE_PLACEHOLDER}ex/processMessage.html">processMessage</a></span> <span class="parameter-name">p</span></span><wbr>'));
3691+
equals('<span class="parameter" id="methodWithTypedefParam-param-p">'
3692+
'<span class="type-annotation"><a href="${HTMLBASE_PLACEHOLDER}ex/processMessage.html">processMessage</a></span> '
3693+
'<span class="parameter-name">p</span></span><wbr>'));
36903694
});
36913695
});
36923696

@@ -3765,20 +3769,34 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans,
37653769
expect(
37663770
forAnnotation.annotations.first,
37673771
equals(
3768-
'@<a href="${HTMLBASE_PLACEHOLDER}ex/ForAnnotation-class.html">ForAnnotation</a>(&#39;my value&#39;)'));
3772+
'@<a href="${HTMLBASE_PLACEHOLDER}ex/ForAnnotation-class.html">ForAnnotation</a>'
3773+
'(&#39;my value&#39;)'));
37693774
});
37703775

37713776
test('methods has the right annotation', () {
37723777
var m = dog.instanceMethods.singleWhere((m) => m.name == 'getClassA');
37733778
expect(m.hasAnnotations, isTrue);
3774-
expect(m.annotations.first, equals('@deprecated'));
3779+
expect(
3780+
m.annotations.first,
3781+
equals(
3782+
'@<a href="${HTMLBASE_PLACEHOLDER}ex/deprecated-constant.html">deprecated</a>'));
37753783
});
37763784

3777-
test('method annotations have the right link and are escaped', () {
3785+
test('constructor annotations have the right link and are escaped', () {
37783786
expect(
37793787
ctr.annotations[0],
37803788
equals(
3781-
'@<a href="${HTMLBASE_PLACEHOLDER}ex/Deprecated-class.html">Deprecated</a>(&quot;Internal use&quot;)'));
3789+
'@<a href="${HTMLBASE_PLACEHOLDER}ex/Deprecated-class.html">Deprecated</a>'
3790+
'(&quot;Internal use&quot;)'));
3791+
});
3792+
3793+
test('const annotations have the right link and are escaped', () {
3794+
var createDog2 =
3795+
dog.staticMethods.firstWhere((c) => c.name == 'createDog2');
3796+
expect(
3797+
createDog2.annotations[0],
3798+
equals(
3799+
'@<a href="${HTMLBASE_PLACEHOLDER}ex/deprecated-constant.html">deprecated</a>'));
37823800
});
37833801
});
37843802

testing/test_package/lib/example.dart

+5
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,11 @@ class Dog implements Cat, E {
447447
return Dog.deprecatedCreate(s);
448448
}
449449

450+
@deprecated
451+
static Dog createDog2(String s) {
452+
return Dog.deprecatedCreate(s);
453+
}
454+
450455
@override
451456
void abstractMethod() {}
452457
}

0 commit comments

Comments
 (0)