Skip to content

Major refactor of dartdoc "features" and annotations #2600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
271 changes: 133 additions & 138 deletions lib/src/generator/templates.renderers.dart

Large diffs are not rendered by default.

87 changes: 87 additions & 0 deletions lib/src/model/annotation.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) 2021, 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/element.dart';
import 'package:dartdoc/src/element_type.dart';
import 'package:dartdoc/src/model/feature.dart';
import 'package:dartdoc/src/model/getter_setter_combo.dart';
import 'package:dartdoc/src/model/library.dart';
import 'package:dartdoc/src/model/model_element.dart';
import 'package:dartdoc/src/model/package_graph.dart';

/// Represents a Dart annotation, attached to an element in the source code with
/// `@`.
class Annotation extends Feature {
final ElementAnnotation annotation;
final Library library;
final PackageGraph packageGraph;

Annotation(this.annotation, this.library, this.packageGraph)
: super(annotation.element.name);

String _linkedNameWithParameters;
@override
String get linkedNameWithParameters => _linkedNameWithParameters ??=
packageGraph.rendererFactory.featureRenderer.renderAnnotation(this);

/// Return the linked name of the annotation.
@override
String get linkedName => annotation.element is PropertyAccessorElement
? ModelElement.fromElement(annotation.element, packageGraph).linkedName
// TODO(jcollins-g): consider linking to constructor instead of type?
: modelType.linkedName;

ElementType _modelType;
ElementType get modelType {
if (_modelType == null) {
var annotatedWith = annotation.element;
if (annotatedWith is ConstructorElement) {
_modelType =
ElementType.from(annotatedWith.returnType, library, packageGraph);
} else if (annotatedWith is PropertyAccessorElement) {
_modelType =
(ModelElement.fromElement(annotatedWith.variable, packageGraph)
as GetterSetterCombo)
.modelType;
} else {
assert(false,
'non-callable element used as annotation?: ${annotation.element}');
}
}
return _modelType;
}

String _parameterText;
String get parameterText {
// TODO(srawlins): Attempt to revive constructor arguments in an annotation,
// akin to source_gen's Reviver, in order to link to inner components. For
// example, in `@Foo(const Bar(), baz: <Baz>[Baz.one, Baz.two])`, link to
// `Foo`, `Bar`, `Baz`, `Baz.one`, and `Baz.two`.
if (_parameterText == null) {
var source = annotation.toSource();
var startIndex = source.indexOf('(');
_parameterText =
source.substring(startIndex == -1 ? source.length : startIndex);
}
return _parameterText;
}

@override
bool get isPublic =>
modelType.isPublic &&
modelType is DefinedElementType &&
!packageGraph.invisibleAnnotations
.contains((modelType as DefinedElementType).element);

@override
bool operator ==(Object other) {
if (other is Annotation) {
return other.annotation == annotation;
}
return false;
}

@override
int get hashCode => annotation.hashCode;
}
4 changes: 2 additions & 2 deletions lib/src/model/constructor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ class Constructor extends ModelElement
@override
String get kind => 'constructor';

DefinedElementType _modelType;
DefinedElementType get modelType =>
CallableElementTypeMixin _modelType;
CallableElementTypeMixin get modelType =>
_modelType ??= ElementType.from(element.type, library, packageGraph);

String _name;
Expand Down
10 changes: 5 additions & 5 deletions lib/src/model/container_member.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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/feature.dart';
import 'package:dartdoc/src/model/model.dart';

/// A [ModelElement] that is a [Container] member.
Expand All @@ -25,11 +26,10 @@ mixin ContainerMember on ModelElement implements EnclosedElement {
}

@override
Set<String> get features {
var _features = super.features;
if (isExtended) _features.add('extended');
return _features;
}
Set<Feature> get features => {
...super.features,
if (isExtended) Feature.extended,
};

bool _canonicalEnclosingContainerIsSet = false;
Container _canonicalEnclosingContainer;
Expand Down
63 changes: 63 additions & 0 deletions lib/src/model/feature.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) 2021, 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:collection/collection.dart';
import 'package:dartdoc/src/model/privacy.dart';

int byFeatureOrdering(Feature a, Feature b) {
if (a.sortGroup < b.sortGroup) return -1;
if (a.sortGroup > b.sortGroup) return 1;
return compareAsciiLowerCaseNatural(a.name, b.name);
}

class ElementFeatureNotFoundError extends Error {
final String message;

ElementFeatureNotFoundError([this.message]);

@override
String toString() => 'ElementFeatureNotFoundError: $message';
}

/// A "feature" includes both explicit annotations in code (e.g. `deprecated`)
/// as well as others added by the documentation system (`read-write`);
class Feature implements Privacy {
final String _name;

/// Do not use this except in subclasses, prefer const members of this
/// class instead.
const Feature(this._name, [this.sortGroup = 0]);

final String featurePrefix = '';

String get name => _name;

String get linkedName => name;

String get linkedNameWithParameters => linkedName;

@override
bool get isPublic => !name.startsWith('_');

/// Numerical sort group for this feature.
/// Less than zero will sort before custom annotations.
/// Above zero will sort after custom annotations.
/// Zero will sort alphabetically among custom annotations.
// TODO(jcollins-g): consider [Comparable]?
final int sortGroup;

static const readOnly = Feature('read-only', 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha I had this exact same idea. Excellent.

static const finalFeature = Feature('final', 2);
static const writeOnly = Feature('write-only', 2);
static const readWrite = Feature('read / write', 2);
static const covariant = Feature('covariant', 2);
static const extended = Feature('extended', 3);
static const inherited = Feature('inherited', 3);
static const inheritedGetter = Feature('inherited-getter', 3);
static const inheritedSetter = Feature('inherited-setter', 3);
static const lateFeature = Feature('late', 3);
static const overrideFeature = Feature('override', 3);
static const overrideGetter = Feature('override-getter', 3);
static const overrideSetter = Feature('override-setter', 3);
}
2 changes: 1 addition & 1 deletion lib/src/model/feature_set.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ mixin FeatureSet {
// legacy interfaces.
if (isNullSafety) {
yield LanguageFeature(
'Null safety', packageGraph.rendererFactory.featureRenderer);
'Null safety', packageGraph.rendererFactory.languageFeatureRenderer);
}
}

Expand Down
35 changes: 12 additions & 23 deletions lib/src/model/field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:dartdoc/src/model/feature.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/render/source_code_renderer.dart';

Expand Down Expand Up @@ -102,47 +103,35 @@ class Field extends ModelElement
@override
String get kind => isConst ? 'constant' : 'property';

@override
List<String> get annotations {
var allAnnotations = [...super.annotations];

if (element is PropertyInducingElement) {
var pie = element as PropertyInducingElement;
allAnnotations.addAll(annotationsFromMetadata(pie.getter?.metadata));
allAnnotations.addAll(annotationsFromMetadata(pie.setter?.metadata));
}
return allAnnotations;
}

String get fullkind {
if (field.isAbstract) return 'abstract $kind';
return kind;
}

@override
Set<String> get features {
Set<Feature> get features {
var allFeatures = super.features..addAll(comboFeatures);
// Combo features can indicate 'inherited' and 'override' if
// either the getter or setter has one of those properties, but that's not
// really specific enough for [Field]s that have public getter/setters.
if (hasPublicGetter && hasPublicSetter) {
if (getter.isInherited && setter.isInherited) {
allFeatures.add('inherited');
allFeatures.add(Feature.inherited);
} else {
allFeatures.remove('inherited');
if (getter.isInherited) allFeatures.add('inherited-getter');
if (setter.isInherited) allFeatures.add('inherited-setter');
allFeatures.remove(Feature.inherited);
if (getter.isInherited) allFeatures.add(Feature.inheritedGetter);
if (setter.isInherited) allFeatures.add(Feature.inheritedSetter);
}
if (getter.isOverride && setter.isOverride) {
allFeatures.add('override');
allFeatures.add(Feature.overrideFeature);
} else {
allFeatures.remove('override');
if (getter.isOverride) allFeatures.add('override-getter');
if (setter.isOverride) allFeatures.add('override-setter');
allFeatures.remove(Feature.overrideFeature);
if (getter.isOverride) allFeatures.add(Feature.overrideGetter);
if (setter.isOverride) allFeatures.add(Feature.overrideSetter);
}
} else {
if (isInherited) allFeatures.add('inherited');
if (isOverride) allFeatures.add('override');
if (isInherited) allFeatures.add(Feature.inherited);
if (isOverride) allFeatures.add(Feature.overrideFeature);
}
return allFeatures;
}
Expand Down
34 changes: 20 additions & 14 deletions lib/src/model/getter_setter_combo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,46 @@

import 'dart:convert';

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/ast.dart'
show Expression, InstanceCreationExpression;
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:dartdoc/src/element_type.dart';
import 'package:dartdoc/src/model/annotation.dart';
import 'package:dartdoc/src/model/feature.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/utils.dart';
import 'package:dartdoc/src/warnings.dart';
import 'package:meta/meta.dart';

/// Mixin for top-level variables and fields (aka properties)
mixin GetterSetterCombo on ModelElement {
Accessor get getter;

Accessor get setter;

@override
Iterable<Annotation> get annotations => [
...super.annotations,
if (hasGetter) ...getter.annotations,
if (hasSetter) ...setter.annotations,
];

Iterable<Accessor> get allAccessors sync* {
for (var a in [getter, setter]) {
if (a != null) yield a;
}
}

Set<String> get comboFeatures {
var allFeatures = <String>{};
if (hasExplicitGetter && hasPublicGetter) {
allFeatures.addAll(getter.features);
}
if (hasExplicitSetter && hasPublicSetter) {
allFeatures.addAll(setter.features);
}
if (readOnly && !isFinal && !isConst) allFeatures.add('read-only');
if (writeOnly) allFeatures.add('write-only');
if (readWrite) allFeatures.add('read / write');
return allFeatures;
}
@protected
Set<Feature> get comboFeatures => {
if (hasExplicitGetter && hasPublicGetter) ...getter.features,
if (hasExplicitSetter && hasPublicSetter) ...setter.features,
if (readOnly && !isFinal && !isConst) Feature.readOnly,
if (writeOnly) Feature.writeOnly,
if (readWrite) Feature.readWrite,
};

@override
ModelElement enclosingElement;
Expand Down
14 changes: 7 additions & 7 deletions lib/src/model/inheritable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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/feature.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/special_elements.dart';

Expand All @@ -28,13 +29,12 @@ mixin Inheritable on ContainerMember {
bool get isCovariant;

@override
Set<String> get features {
var _features = super.features;
if (isOverride) _features.add('override');
if (isInherited) _features.add('inherited');
if (isCovariant) _features.add('covariant');
return _features;
}
Set<Feature> get features => {
...super.features,
if (isOverride) Feature.overrideFeature,
if (isInherited) Feature.inherited,
if (isCovariant) Feature.covariant,
};

@override
Library get canonicalLibrary => canonicalEnclosingContainer?.canonicalLibrary;
Expand Down
6 changes: 3 additions & 3 deletions lib/src/model/language_feature.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// 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/render/feature_renderer.dart';
import 'package:dartdoc/src/render/language_feature_renderer.dart';

const Map<String, String> _featureDescriptions = {
'Null safety': 'Supports the null safety language feature.',
Expand All @@ -23,12 +23,12 @@ class LanguageFeature {
String /*?*/ get featureUrl => _featureUrls[name];

/// The rendered label for this language feature.
String get featureLabel => _featureRenderer.renderFeatureLabel(this);
String get featureLabel => _featureRenderer.renderLanguageFeatureLabel(this);

/// The name of this language feature.
final String name;

final FeatureRenderer _featureRenderer;
final LanguageFeatureRenderer _featureRenderer;

LanguageFeature(this.name, this._featureRenderer) {
assert(_featureDescriptions.containsKey(name));
Expand Down
10 changes: 5 additions & 5 deletions lib/src/model/method.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/dart/element/member.dart' show ExecutableMember;
import 'package:dartdoc/src/element_type.dart';
import 'package:dartdoc/src/model/feature.dart';
import 'package:dartdoc/src/model/model.dart';

class Method extends ModelElement
Expand Down Expand Up @@ -81,11 +82,10 @@ class Method extends ModelElement
bool get isOperator => false;

@override
Set<String> get features {
var allFeatures = super.features;
if (isInherited) allFeatures.add('inherited');
return allFeatures;
}
Set<Feature> get features => {
...super.features,
if (isInherited) Feature.inherited,
};

@override
bool get isStatic => element.isStatic;
Expand Down
Loading