blob: 323e58a2fa7fd5bd38eb2b3b361af22d65d49d8b [file] [log] [blame]
// Copyright (c) 2014, 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.
/// The models used to represent Dart code.
library dartdoc.models;
import 'dart:async';
import 'dart:collection' show UnmodifiableListView;
import 'dart:convert';
import 'dart:io';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart'
show
AnnotatedNode,
AstNode,
CommentReference,
CompilationUnit,
Expression,
InstanceCreationExpression;
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:analyzer/file_system/file_system.dart' as fileSystem;
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/src/source/package_map_resolver.dart';
import 'package:analyzer/src/source/sdk_ext.dart';
// TODO(jcollins-g): Stop using internal analyzer structures somehow.
import 'package:analyzer/src/context/builder.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/handle.dart';
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:analyzer/src/generated/engine.dart' hide AnalysisResult;
import 'package:analyzer/src/generated/java_io.dart';
import 'package:analyzer/src/generated/resolver.dart'
show Namespace, NamespaceBuilder, InheritanceManager;
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/source_io.dart';
import 'package:analyzer/src/dart/element/member.dart'
show ExecutableMember, Member, ParameterMember;
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:args/args.dart';
import 'package:collection/collection.dart';
import 'package:crypto/crypto.dart';
import 'package:dartdoc/src/dartdoc_options.dart';
import 'package:dartdoc/src/element_type.dart';
import 'package:dartdoc/src/io_utils.dart';
import 'package:dartdoc/src/line_number_cache.dart';
import 'package:dartdoc/src/logging.dart';
import 'package:dartdoc/src/markdown_processor.dart' show Documentation;
import 'package:dartdoc/src/model_utils.dart';
import 'package:dartdoc/src/package_meta.dart' show PackageMeta, FileContents;
import 'package:dartdoc/src/special_elements.dart';
import 'package:dartdoc/src/tuple.dart';
import 'package:dartdoc/src/utils.dart';
import 'package:dartdoc/src/warnings.dart';
import 'package:path/path.dart' as pathLib;
import 'package:pub_semver/pub_semver.dart';
import 'package:package_config/discovery.dart' as package_config;
import 'package:quiver/iterables.dart' as quiverIterables;
int byName(Nameable a, Nameable b) =>
compareAsciiLowerCaseNatural(a.name, b.name);
/// Items mapped less than zero will sort before custom annotations.
/// Items mapped above zero are sorted after custom annotations.
/// Items mapped to zero will sort alphabetically among custom annotations.
/// Custom annotations are assumed to be any annotation or feature not in this
/// map.
const Map<String, int> featureOrder = const {
'read-only': 1,
'write-only': 1,
'read / write': 1,
'covariant': 2,
'final': 2,
'inherited': 3,
'inherited-getter': 3,
'inherited-setter': 3,
'override': 3,
'override-getter': 3,
'override-setter': 3,
};
int byFeatureOrdering(String a, String b) {
int scoreA = 0;
int scoreB = 0;
if (featureOrder.containsKey(a)) scoreA = featureOrder[a];
if (featureOrder.containsKey(b)) scoreB = featureOrder[b];
if (scoreA < scoreB) return -1;
if (scoreA > scoreB) return 1;
return compareAsciiLowerCaseNatural(a, b);
}
final RegExp locationSplitter = new RegExp(r'(package:|[\\/;.])');
final RegExp substituteNameVersion = new RegExp(r'%([bnv])%');
/// This doc may need to be processed in case it has a template or html
/// fragment.
final needsPrecacheRegExp = new RegExp(r'{@(template|tool|inject-html)');
final templateRegExp = new RegExp(
r'[ ]*{@template\s+(.+?)}([\s\S]+?){@endtemplate}[ ]*\n?',
multiLine: true);
final htmlRegExp = new RegExp(
r'[ ]*{@inject-html\s*}([\s\S]+?){@end-inject-html}[ ]*\n?',
multiLine: true);
final htmlInjectRegExp =
new RegExp(r'<dartdoc-html>([a-f0-9]+)</dartdoc-html>');
// Matches all tool directives (even some invalid ones). This is so
// we can give good error messages if the directive is malformed, instead of
// just silently emitting it as-is.
final basicToolRegExp = new RegExp(
r'[ ]*{@tool\s+([^}]+)}\n?([\s\S]+?)\n?{@end-tool}[ ]*\n?',
multiLine: true);
/// Regexp to take care of splitting arguments, and handling the quotes
/// around arguments, if any.
///
/// Match group 1 is the "foo=" (or "--foo=") part of the option, if any.
/// Match group 2 contains the quote character used (which is discarded).
/// Match group 3 is a quoted arg, if any, without the quotes.
/// Match group 4 is the unquoted arg, if any.
final RegExp argMatcher = new RegExp(r'([a-zA-Z\-_0-9]+=)?' // option name
r'(?:' // Start a new non-capture group for the two possibilities.
r'''(["'])((?:\\{2})*|(?:.*?[^\\](?:\\{2})*))\2|''' // with quotes.
r'([^ ]+))'); // without quotes.
final categoryRegexp = new RegExp(
r'[ ]*{@(api|category|subCategory|image|samples) (.+?)}[ ]*\n?',
multiLine: true);
final macroRegExp = new RegExp(r'{@macro\s+([^}]+)}');
/// Mixin for subclasses of ModelElement representing Elements that can be
/// inherited from one class to another.
///
/// Inheritable adds yet another view to help canonicalization for member
/// [ModelElement]s -- [Inheritable.definingEnclosingElement]. With this
/// as an end point, we can search the inheritance chain between this instance and
/// the [Inheritable.definingEnclosingElement] in [Inheritable.canonicalEnclosingElement],
/// for the canonical [Class] closest to where this member was defined. We
/// can then know that when we find [Inheritable.element] inside that [Class]'s
/// namespace, that's the one we should treat as canonical and implementors
/// of this class can use that knowledge to determine canonicalization.
///
/// We pick the class closest to the definingEnclosingElement so that all
/// children of that class inheriting the same member will point to the same
/// place in the documentation, and we pick a canonical class because that's
/// the one in the public namespace that will be documented.
abstract class Inheritable implements ModelElement {
bool get isInherited;
bool _canonicalEnclosingClassIsSet = false;
Class _canonicalEnclosingClass;
Class _definingEnclosingClass;
ModelElement get definingEnclosingElement {
if (_definingEnclosingClass == null) {
_definingEnclosingClass =
new ModelElement.fromElement(element.enclosingElement, packageGraph);
}
return _definingEnclosingClass;
}
@override
ModelElement _buildCanonicalModelElement() {
return canonicalEnclosingElement?.allCanonicalModelElements?.firstWhere(
(m) => m.name == name && m.isPropertyAccessor == isPropertyAccessor,
orElse: () => null);
}
Class get canonicalEnclosingElement {
Element searchElement = element;
if (!_canonicalEnclosingClassIsSet) {
if (isInherited) {
searchElement = searchElement is Member
? PackageGraph.getBasestElement(searchElement)
: searchElement;
// TODO(jcollins-g): generate warning if an inherited element's definition
// is in an intermediate non-canonical class in the inheritance chain?
Class previous;
Class previousNonSkippable;
for (Class c in inheritance.reversed) {
// Filter out mixins.
if (c.contains(searchElement)) {
if ((packageGraph.inheritThrough.contains(previous) &&
c != definingEnclosingElement) ||
(packageGraph.inheritThrough.contains(c) &&
c == definingEnclosingElement)) {
return (previousNonSkippable.memberByExample(this) as Inheritable)
.canonicalEnclosingElement;
}
Class canonicalC =
packageGraph.findCanonicalModelElementFor(c.element);
// TODO(jcollins-g): invert this lookup so traversal is recursive
// starting from the ModelElement.
if (canonicalC != null) {
assert(canonicalC.isCanonical);
assert(canonicalC.contains(searchElement));
_canonicalEnclosingClass = canonicalC;
break;
}
}
previous = c;
if (!packageGraph.inheritThrough.contains(c)) {
previousNonSkippable = c;
}
}
// This is still OK because we're never supposed to cloak public
// classes.
if (definingEnclosingElement.isCanonical &&
definingEnclosingElement.isPublic) {
assert(definingEnclosingElement == _canonicalEnclosingClass);
}
} else {
_canonicalEnclosingClass =
packageGraph.findCanonicalModelElementFor(enclosingElement.element);
}
_canonicalEnclosingClassIsSet = true;
assert(_canonicalEnclosingClass == null ||
_canonicalEnclosingClass.isDocumented);
}
assert(_canonicalEnclosingClass == null ||
(_canonicalEnclosingClass.isDocumented));
return _canonicalEnclosingClass;
}
@override
Set<String> get features {
Set<String> _features = _baseFeatures();
if (isOverride) _features.add('override');
if (isInherited) _features.add('inherited');
if (isCovariant) _features.add('covariant');
return _features;
}
bool get isCovariant;
List<Class> get inheritance {
List<Class> inheritance = [];
inheritance.addAll((enclosingElement as Class).inheritanceChain);
Class object = packageGraph.specialClasses[SpecialClass.object];
if (!inheritance.contains(definingEnclosingElement) &&
definingEnclosingElement != null) {
assert(definingEnclosingElement == object);
}
// Unless the code explicitly extends dart-core's Object, we won't get
// an entry here. So add it.
if (inheritance.last != object && object != null) {
inheritance.add(object);
}
assert(inheritance.where((e) => e == object).length == 1);
return inheritance;
}
Inheritable get overriddenElement;
bool _isOverride;
bool get isOverride {
if (_isOverride == null) {
// The canonical version of the enclosing element -- not canonicalEnclosingElement,
// as that is the element enclosing the canonical version of this element,
// two different things. Defaults to the enclosing element.
//
// We use canonical elements here where possible to deal with reexports
// as seen in Flutter.
Class enclosingCanonical = enclosingElement;
if (enclosingElement is ModelElement) {
enclosingCanonical =
(enclosingElement as ModelElement).canonicalModelElement;
}
// The class in which this element was defined, canonical if available.
Class definingCanonical =
definingEnclosingElement.canonicalModelElement ??
definingEnclosingElement;
// The canonical version of the element we're overriding, if available.
ModelElement overriddenCanonical =
overriddenElement?.canonicalModelElement ?? overriddenElement;
// We have to have an overridden element for it to be possible for this
// element to be an override.
_isOverride = overriddenElement != null &&
// The defining class and the enclosing class for this element
// must be the same (element is defined here).
enclosingCanonical == definingCanonical &&
// If the overridden element isn't public, we shouldn't be an
// override in most cases. Approximation until #1623 is fixed.
overriddenCanonical.isPublic;
assert(!(_isOverride && isInherited));
}
return _isOverride;
}
int _overriddenDepth;
@override
int get overriddenDepth {
if (_overriddenDepth == null) {
_overriddenDepth = 0;
Inheritable e = this;
while (e.overriddenElement != null) {
_overriddenDepth += 1;
e = e.overriddenElement;
}
}
return _overriddenDepth;
}
}
/// A getter or setter that is a member of a Class.
class InheritableAccessor extends Accessor with Inheritable {
/// Factory will return an [InheritableAccessor] with isInherited = true
/// if [element] is in [inheritedAccessors].
factory InheritableAccessor.from(PropertyAccessorElement element,
Set<PropertyAccessorElement> inheritedAccessors, Class enclosingClass) {
InheritableAccessor accessor;
if (element == null) return null;
if (inheritedAccessors.contains(element)) {
accessor = new ModelElement.from(
element, enclosingClass.library, enclosingClass.packageGraph,
enclosingClass: enclosingClass);
} else {
accessor = new ModelElement.from(
element, enclosingClass.library, enclosingClass.packageGraph);
}
return accessor;
}
ModelElement _enclosingElement;
bool _isInherited = false;
InheritableAccessor(PropertyAccessorElement element, Library library,
PackageGraph packageGraph)
: super(element, library, packageGraph, null);
InheritableAccessor.inherited(PropertyAccessorElement element,
Library library, PackageGraph packageGraph, this._enclosingElement,
{Member originalMember})
: super(element, library, packageGraph, originalMember) {
_isInherited = true;
}
@override
bool get isInherited => _isInherited;
@override
Class get enclosingElement {
if (_enclosingElement == null) {
_enclosingElement = super.enclosingElement;
}
return _enclosingElement;
}
bool _overriddenElementIsSet = false;
ModelElement _overriddenElement;
@override
InheritableAccessor get overriddenElement {
assert(packageGraph.allLibrariesAdded);
if (!_overriddenElementIsSet) {
_overriddenElementIsSet = true;
Element parent = element.enclosingElement;
if (parent is ClassElement) {
for (InterfaceType t in parent.allSupertypes) {
Element accessor = this.isGetter
? t.getGetter(element.name)
: t.getSetter(element.name);
if (accessor != null) {
if (accessor is Member) {
accessor = PackageGraph.getBasestElement(accessor);
}
Class parentClass =
new ModelElement.fromElement(t.element, packageGraph);
List<Field> possibleFields = [];
possibleFields.addAll(parentClass.allInstanceFields);
possibleFields.addAll(parentClass.staticProperties);
String fieldName = accessor.name.replaceFirst('=', '');
Field foundField = possibleFields.firstWhere(
(f) => f.element.name == fieldName,
orElse: () => null);
if (foundField != null) {
if (this.isGetter) {
_overriddenElement = foundField.getter;
} else {
_overriddenElement = foundField.setter;
}
assert(!(_overriddenElement as Accessor).isInherited);
break;
}
}
}
}
}
return _overriddenElement;
}
}
/// Getters and setters.
class Accessor extends ModelElement implements EnclosedElement {
GetterSetterCombo _enclosingCombo;
Accessor(PropertyAccessorElement element, Library library,
PackageGraph packageGraph, Member originalMember)
: super(element, library, packageGraph, originalMember);
String get linkedReturnType {
assert(isGetter);
return modelType.createLinkedReturnTypeName();
}
GetterSetterCombo get enclosingCombo {
if (_enclosingCombo == null) {
if (enclosingElement is Class) {
// TODO(jcollins-g): this side effect is rather hacky. Make sure
// enclosingCombo always gets set at accessor creation time, somehow, to
// avoid this.
// TODO(jcollins-g): This also doesn't work for private accessors sometimes.
(enclosingElement as Class).allInstanceFields;
}
assert(_enclosingCombo != null);
}
return _enclosingCombo;
}
/// Call exactly once to set the enclosing combo for this Accessor.
set enclosingCombo(GetterSetterCombo combo) {
assert(_enclosingCombo == null || combo == _enclosingCombo);
assert(combo != null);
_enclosingCombo = combo;
}
bool get isSynthetic => element.isSynthetic;
@override
String get sourceCode {
if (_sourceCode == null) {
if (isSynthetic) {
_sourceCode = packageGraph
._getModelNodeFor((element as PropertyAccessorElement).variable)
.sourceCode;
} else {
_sourceCode = super.sourceCode;
}
}
return _sourceCode;
}
@override
List<ModelElement> get computeDocumentationFrom {
return super.computeDocumentationFrom;
}
@override
String _computeDocumentationComment() {
if (isSynthetic) {
String docComment =
(element as PropertyAccessorElement).variable.documentationComment;
// If we're a setter, only display something if we have something different than the getter.
// TODO(jcollins-g): modify analyzer to do this itself?
if (isGetter ||
// TODO(jcollins-g): @nodoc reading from comments is at the wrong abstraction level here.
(docComment != null &&
(docComment.contains('<nodoc>') ||
docComment.contains('@nodoc'))) ||
(isSetter &&
enclosingCombo.hasGetter &&
enclosingCombo.getter.documentationComment != docComment)) {
return stripComments(docComment);
} else {
return '';
}
}
return stripComments(super._computeDocumentationComment());
}
@override
void warn(PackageWarning kind,
{String message,
Iterable<Locatable> referredFrom,
Iterable<String> extendedDebug}) {
if (enclosingCombo != null) {
enclosingCombo.warn(kind,
message: message,
referredFrom: referredFrom,
extendedDebug: extendedDebug);
} else {
super.warn(kind,
message: message,
referredFrom: referredFrom,
extendedDebug: extendedDebug);
}
}
@override
ModelElement get enclosingElement {
if (_accessor.enclosingElement is CompilationUnitElement) {
return packageGraph.findButDoNotCreateLibraryFor(
_accessor.enclosingElement.enclosingElement);
}
return new ModelElement.from(
_accessor.enclosingElement, library, packageGraph);
}
@override
Set<String> get features {
if (!isCovariant) return super.features;
return super.features..add('covariant');
}
@override
bool get isCanonical => enclosingCombo.isCanonical;
bool get isCovariant => isSetter && parameters.first.isCovariant;
bool get isInherited => false;
@override
String get href {
return enclosingCombo.href;
}
bool get isGetter => _accessor.isGetter;
bool get isSetter => _accessor.isSetter;
@override
String get kind => 'accessor';
@override
String get namePart {
if (_namePart == null) {
_namePart = super.namePart.split('=').first;
}
return _namePart;
}
PropertyAccessorElement get _accessor => (element as PropertyAccessorElement);
}
/// Implements the Dart 2.1 "mixin" style of mixin declarations.
class Mixin extends Class {
Mixin(ClassElement element, Library library, PackageGraph packageGraph)
: super(element, library, packageGraph);
@override
bool get isAbstract => false;
@override
List<Class> get inheritanceChain {
if (_inheritanceChain == null) {
_inheritanceChain = [];
_inheritanceChain.add(this);
// Mix-in interfaces come before other interfaces.
_inheritanceChain.addAll(superclassConstraints
.expand((ParameterizedElementType i) =>
(i.element as Class).inheritanceChain)
.where((Class c) =>
c != packageGraph.specialClasses[SpecialClass.object]));
// Interfaces need to come last, because classes in the superChain might
// implement them even when they aren't mentioned.
_inheritanceChain.addAll(
interfaces.expand((e) => (e.element as Class).inheritanceChain));
}
return _inheritanceChain.toList(growable: false);
}
List<ParameterizedElementType> _superclassConstraints;
/// Returns a list of superclass constraints for this mixin.
Iterable<ParameterizedElementType> get superclassConstraints {
if (_superclassConstraints == null) {
_superclassConstraints = (element as ClassElement)
.superclassConstraints
.map<ParameterizedElementType>(
(InterfaceType i) => new ElementType.from(i, packageGraph))
.toList();
}
return _superclassConstraints;
}
bool get hasPublicSuperclassConstraints =>
publicSuperclassConstraints.isNotEmpty;
Iterable<ParameterizedElementType> get publicSuperclassConstraints =>
filterNonPublic(superclassConstraints);
@override
bool get hasModifiers => super.hasModifiers || hasPublicSuperclassConstraints;
@override
String get fileName => "${name}-mixin.html";
@override
String get kind => 'mixin';
}
class Class extends ModelElement
with TypeParameters, Categorization
implements EnclosedElement {
List<DefinedElementType> _mixins;
DefinedElementType _supertype;
List<DefinedElementType> _interfaces;
List<Constructor> _constructors;
List<Method> _allMethods;
List<Operator> _operators;
List<Operator> _inheritedOperators;
List<Method> _inheritedMethods;
List<Method> _staticMethods;
List<Method> _instanceMethods;
List<Field> _fields;
List<Field> _staticFields;
List<Field> _constants;
List<Field> _instanceFields;
List<Field> _inheritedProperties;
Class(ClassElement element, Library library, PackageGraph packageGraph)
: super(element, library, packageGraph, null) {
packageGraph.specialClasses.addSpecial(this);
_mixins = _cls.mixins
.map((f) {
DefinedElementType t = new ElementType.from(f, packageGraph);
return t;
})
.where((mixin) => mixin != null)
.toList(growable: false);
if (_cls.supertype != null && _cls.supertype.element.supertype != null) {
_supertype = new ElementType.from(_cls.supertype, packageGraph);
}
_interfaces = _cls.interfaces
.map((f) => new ElementType.from(f, packageGraph) as DefinedElementType)
.toList(growable: false);
}
Iterable<Method> get allInstanceMethods =>
quiverIterables.concat([instanceMethods, inheritedMethods]);
Iterable<Method> get allPublicInstanceMethods =>
filterNonPublic(allInstanceMethods);
bool get allPublicInstanceMethodsInherited =>
instanceMethods.every((f) => f.isInherited);
Iterable<Field> get allInstanceFields =>
quiverIterables.concat([instanceProperties, inheritedProperties]);
Iterable<Accessor> get allAccessors => quiverIterables.concat([
allInstanceFields.expand((f) => f.allAccessors),
constants.map((c) => c.getter)
]);
Iterable<Field> get allPublicInstanceProperties =>
filterNonPublic(allInstanceFields);
bool get allPublicInstancePropertiesInherited =>
allPublicInstanceProperties.every((f) => f.isInherited);
Iterable<Operator> get allOperators =>
quiverIterables.concat([operators, inheritedOperators]);
Iterable<Operator> get allPublicOperators => filterNonPublic(allOperators);
bool get allPublicOperatorsInherited =>
allPublicOperators.every((f) => f.isInherited);
List<Field> get constants {
if (_constants != null) return _constants;
_constants = _allFields.where((f) => f.isConst).toList(growable: false)
..sort(byName);
return _constants;
}
Iterable<Field> get publicConstants => filterNonPublic(constants);
Map<Element, ModelElement> _allElements;
Map<Element, ModelElement> get allElements {
if (_allElements == null) {
_allElements = new Map();
for (ModelElement me in allModelElements) {
assert(!_allElements.containsKey(me.element));
_allElements[me.element] = me;
}
}
return _allElements;
}
Map<String, List<ModelElement>> _allModelElementsByNamePart;
/// Helper for [_MarkdownCommentReference._getResultsForClass].
Map<String, List<ModelElement>> get allModelElementsByNamePart {
if (_allModelElementsByNamePart == null) {
_allModelElementsByNamePart = {};
for (ModelElement me in allModelElements) {
_allModelElementsByNamePart.update(
me.namePart, (List<ModelElement> v) => v..add(me),
ifAbsent: () => <ModelElement>[me]);
}
}
return _allModelElementsByNamePart;
}
/// This class might be canonical for elements it does not contain.
/// See [Inheritable.canonicalEnclosingElement].
bool contains(Element element) => allElements.containsKey(element);
ModelElement findModelElement(Element element) => allElements[element];
Map<String, List<ModelElement>> _membersByName;
/// Given a ModelElement that is a member of some other class, return
/// a member of this class that has the same name and return type.
///
/// This enables object substitution for canonicalization, such as Interceptor
/// for Object.
ModelElement memberByExample(ModelElement example) {
if (_membersByName == null) {
_membersByName = new Map();
for (ModelElement me in allModelElements) {
if (!_membersByName.containsKey(me.name))
_membersByName[me.name] = new List();
_membersByName[me.name].add(me);
}
}
ModelElement member;
Iterable<ModelElement> possibleMembers = _membersByName[example.name]
.where((e) => e.runtimeType == example.runtimeType);
if (example.runtimeType == Accessor) {
possibleMembers = possibleMembers.where(
(e) => (example as Accessor).isGetter == (e as Accessor).isGetter);
}
member = possibleMembers.first;
assert(possibleMembers.length == 1);
return member;
}
List<ModelElement> _allModelElements;
List<ModelElement> get allModelElements {
if (_allModelElements == null) {
_allModelElements = new List.from(
quiverIterables.concat([
allInstanceMethods,
allInstanceFields,
allAccessors,
allOperators,
constants,
constructors,
staticMethods,
staticProperties,
typeParameters,
]),
growable: false);
}
return _allModelElements;
}
List<ModelElement> _allCanonicalModelElements;
List<ModelElement> get allCanonicalModelElements {
return (_allCanonicalModelElements ??=
allModelElements.where((e) => e.isCanonical).toList());
}
List<Constructor> get constructors {
if (_constructors != null) return _constructors;
_constructors = _cls.constructors.map((e) {
return new ModelElement.from(e, library, packageGraph) as Constructor;
}).toList(growable: true)
..sort(byName);
return _constructors;
}
Iterable<Constructor> get publicConstructors => filterNonPublic(constructors);
/// Returns the library that encloses this element.
@override
ModelElement get enclosingElement => library;
@override
String get fileName => "${name}-class.html";
String get fullkind {
if (isAbstract) return 'abstract $kind';
return kind;
}
bool get hasPublicConstants => publicConstants.isNotEmpty;
bool get hasPublicConstructors => publicConstructors.isNotEmpty;
bool get hasPublicImplementors => publicImplementors.isNotEmpty;
bool get hasInstanceMethods => instanceMethods.isNotEmpty;
bool get hasInstanceProperties => instanceProperties.isNotEmpty;
bool get hasPublicInterfaces => publicInterfaces.isNotEmpty;
bool get hasPublicMethods =>
publicInstanceMethods.isNotEmpty || publicInheritedMethods.isNotEmpty;
bool get hasPublicMixins => publicMixins.isNotEmpty;
bool get hasModifiers =>
hasPublicMixins ||
hasAnnotations ||
hasPublicInterfaces ||
hasPublicSuperChainReversed ||
hasPublicImplementors;
bool get hasPublicOperators =>
publicOperators.isNotEmpty || publicInheritedOperators.isNotEmpty;
bool get hasPublicProperties =>
publicInheritedProperties.isNotEmpty ||
publicInstanceProperties.isNotEmpty;
bool get hasPublicStaticMethods => publicStaticMethods.isNotEmpty;
bool get hasPublicStaticProperties => publicStaticProperties.isNotEmpty;
bool get hasPublicSuperChainReversed => publicSuperChainReversed.isNotEmpty;
@override
String get href {
if (!identical(canonicalModelElement, this))
return canonicalModelElement?.href;
assert(canonicalLibrary != null);
assert(canonicalLibrary == library);
return '${package.baseHref}${library.dirName}/$fileName';
}
/// Returns all the implementors of this class.
Iterable<Class> get publicImplementors {
return filterNonPublic(findCanonicalFor(
packageGraph.implementors[href] != null
? packageGraph.implementors[href]
: []));
}
List<Method> get inheritedMethods {
if (_inheritedMethods == null) {
_inheritedMethods = <Method>[];
Set<String> methodNames = _methods.map((m) => m.element.name).toSet();
Set<ExecutableElement> inheritedMethodElements =
_inheritedElements.where((e) {
return (e is MethodElement &&
!e.isOperator &&
e is! PropertyAccessorElement &&
!methodNames.contains(e.name));
}).toSet();
for (ExecutableElement e in inheritedMethodElements) {
Method m = new ModelElement.from(e, library, packageGraph,
enclosingClass: this);
_inheritedMethods.add(m);
}
_inheritedMethods.sort(byName);
}
return _inheritedMethods;
}
Iterable get publicInheritedMethods => filterNonPublic(inheritedMethods);
bool get hasPublicInheritedMethods => publicInheritedMethods.isNotEmpty;
List<Operator> get inheritedOperators {
if (_inheritedOperators == null) {
_inheritedOperators = [];
Set<String> operatorNames = _operators.map((o) => o.element.name).toSet();
Set<ExecutableElement> inheritedOperatorElements =
_inheritedElements.where((e) {
return (e is MethodElement &&
e.isOperator &&
!operatorNames.contains(e.name));
}).toSet();
for (ExecutableElement e in inheritedOperatorElements) {
Operator o = new ModelElement.from(e, library, packageGraph,
enclosingClass: this);
_inheritedOperators.add(o);
}
_inheritedOperators.sort(byName);
}
return _inheritedOperators;
}
Iterable<Operator> get publicInheritedOperators =>
filterNonPublic(inheritedOperators);
List<Field> get inheritedProperties {
if (_inheritedProperties == null) {
_inheritedProperties = _allFields.where((f) => f.isInherited).toList()
..sort(byName);
}
return _inheritedProperties;
}
Iterable<Field> get publicInheritedProperties =>
filterNonPublic(inheritedProperties);
List<Method> get instanceMethods {
if (_instanceMethods != null) return _instanceMethods;
_instanceMethods = _methods
.where((m) => !m.isStatic && !m.isOperator)
.toList(growable: false)
..sort(byName);
return _instanceMethods;
}
Iterable<Method> get publicInstanceMethods => instanceMethods;
List<Field> get instanceProperties {
if (_instanceFields != null) return _instanceFields;
_instanceFields = _allFields
.where((f) => !f.isStatic && !f.isInherited && !f.isConst)
.toList(growable: false)
..sort(byName);
return _instanceFields;
}
Iterable<Field> get publicInstanceProperties =>
filterNonPublic(instanceProperties);
List<DefinedElementType> get interfaces => _interfaces;
Iterable<DefinedElementType> get publicInterfaces =>
filterNonPublic(interfaces);
List<DefinedElementType> _interfaceChain;
List<DefinedElementType> get interfaceChain {
if (_interfaceChain == null) {
_interfaceChain = [];
for (DefinedElementType interface in interfaces) {
_interfaceChain.add(interface);
_interfaceChain.addAll((interface.element as Class).interfaceChain);
}
}
return _interfaceChain;
}
bool get isAbstract => _cls.isAbstract;
@override
bool get isCanonical => super.isCanonical && isPublic;
bool get isErrorOrException {
bool _doCheck(InterfaceType type) {
return (type.element.library.isDartCore &&
(type.name == 'Exception' || type.name == 'Error'));
}
// if this class is itself Error or Exception, return true
if (_doCheck(_cls.type)) return true;
return _cls.allSupertypes.any(_doCheck);
}
/// Returns true if [other] is a parent class for this class.
bool isInheritingFrom(Class other) =>
superChain.map((et) => (et.element as Class)).contains(other);
@override
String get kind => 'class';
List<DefinedElementType> get mixins => _mixins;
Iterable<DefinedElementType> get publicMixins => filterNonPublic(mixins);
@override
DefinedElementType get modelType => super.modelType;
List<Operator> get operators {
if (_operators != null) return _operators;
_operators = _methods
.where((m) => m.isOperator)
.cast<Operator>()
.toList(growable: false)
..sort(byName);
return _operators;
}
Iterable<Operator> get publicOperators => filterNonPublic(operators);
List<Method> get staticMethods {
if (_staticMethods != null) return _staticMethods;
_staticMethods = _methods.where((m) => m.isStatic).toList(growable: false)
..sort(byName);
return _staticMethods;
}
Iterable<Method> get publicStaticMethods => filterNonPublic(staticMethods);
List<Field> get staticProperties {
if (_staticFields != null) return _staticFields;
_staticFields = _allFields
.where((f) => f.isStatic && !f.isConst)
.toList(growable: false)
..sort(byName);
return _staticFields;
}
Iterable<Field> get publicStaticProperties =>
filterNonPublic(staticProperties);
/// 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<Class> _inheritanceChain;
List<Class> get inheritanceChain {
if (_inheritanceChain == null) {
_inheritanceChain = [];
_inheritanceChain.add(this);
/// Caching should make this recursion a little less painful.
for (Class c in mixins.reversed.map((e) => (e.element as Class))) {
_inheritanceChain.addAll(c.inheritanceChain);
}
for (Class c in superChain.map((e) => (e.element as Class))) {
_inheritanceChain.addAll(c.inheritanceChain);
}
/// Interfaces need to come last, because classes in the superChain might
/// implement them even when they aren't mentioned.
_inheritanceChain.addAll(
interfaces.expand((e) => (e.element as Class).inheritanceChain));
}
return _inheritanceChain.toList(growable: false);
}
List<DefinedElementType> get superChain {
List<DefinedElementType> typeChain = [];
DefinedElementType parent = _supertype;
while (parent != null) {
typeChain.add(parent);
if (parent.type is InterfaceType) {
// Avoid adding [Object] to the superChain (_supertype already has this
// check)
if ((parent.type as InterfaceType)?.superclass?.superclass == null) {
parent = null;
} else {
parent = new ElementType.from(
(parent.type as InterfaceType).superclass, packageGraph);
}
} else {
parent = (parent.element as Class)._supertype;
}
}
return typeChain;
}
Iterable<DefinedElementType> get superChainReversed => superChain.reversed;
Iterable<DefinedElementType> get publicSuperChain =>
filterNonPublic(superChain);
Iterable<DefinedElementType> get publicSuperChainReversed =>
publicSuperChain.toList().reversed;
DefinedElementType get supertype => _supertype;
List<ExecutableElement> __inheritedElements;
List<ExecutableElement> get _inheritedElements {
if (__inheritedElements == null) {
Map<String, ExecutableElement> cmap = definingLibrary.inheritanceManager
.getMembersInheritedFromClasses(element);
Map<String, ExecutableElement> imap = definingLibrary.inheritanceManager
.getMembersInheritedFromInterfaces(element);
__inheritedElements = new List.from(cmap.values)
..addAll(imap.values.where((e) => !cmap.containsKey(e.name)));
}
return __inheritedElements;
}
/// Internal only because subclasses are allowed to override how
/// these are mapped to [allInheritedFields] and so forth.
List<Field> get _allFields {
if (_fields != null) return _fields;
_fields = [];
Set<PropertyAccessorElement> inheritedAccessors = new Set()
..addAll(_inheritedElements
.where((e) => e is PropertyAccessorElement)
.cast<PropertyAccessorElement>());
// This structure keeps track of inherited accessors, allowing lookup
// by field name (stripping the '=' from setters).
Map<String, List<PropertyAccessorElement>> accessorMap = new Map();
for (PropertyAccessorElement accessorElement in inheritedAccessors) {
String name = accessorElement.name.replaceFirst('=', '');
accessorMap.putIfAbsent(name, () => []);
accessorMap[name].add(accessorElement);
}
// For half-inherited fields, the analyzer only links the non-inherited
// to the [FieldElement]. Compose our [Field] class by hand by looking up
// inherited accessors that may be related.
for (FieldElement f in _cls.fields) {
PropertyAccessorElement getterElement = f.getter;
if (getterElement == null && accessorMap.containsKey(f.name)) {
getterElement = accessorMap[f.name]
.firstWhere((e) => e.isGetter, orElse: () => null);
}
PropertyAccessorElement setterElement = f.setter;
if (setterElement == null && accessorMap.containsKey(f.name)) {
setterElement = accessorMap[f.name]
.firstWhere((e) => e.isSetter, orElse: () => null);
}
_addSingleField(getterElement, setterElement, inheritedAccessors, f);
accessorMap.remove(f.name);
}
// Now we only have inherited accessors who aren't associated with
// anything in cls._fields.
for (String fieldName in accessorMap.keys) {
List<PropertyAccessorElement> elements = accessorMap[fieldName].toList();
PropertyAccessorElement getterElement =
elements.firstWhere((e) => e.isGetter, orElse: () => null);
PropertyAccessorElement setterElement =
elements.firstWhere((e) => e.isSetter, orElse: () => null);
_addSingleField(getterElement, setterElement, inheritedAccessors);
}
_fields.sort(byName);
return _fields;
}
/// Add a single Field to _fields.
///
/// If [f] is not specified, pick the FieldElement from the PropertyAccessorElement
/// whose enclosing class inherits from the other (defaulting to the getter)
/// and construct a Field using that.
void _addSingleField(
PropertyAccessorElement getterElement,
PropertyAccessorElement setterElement,
Set<PropertyAccessorElement> inheritedAccessors,
[FieldElement f]) {
Accessor getter =
new InheritableAccessor.from(getterElement, inheritedAccessors, this);
Accessor setter =
new InheritableAccessor.from(setterElement, inheritedAccessors, this);
// Rebind getterElement/setterElement as ModelElement.from can resolve
// MultiplyInheritedExecutableElements or resolve Members.
getterElement = getter?.element;
setterElement = setter?.element;
assert(!(getter == null && setter == null));
if (f == null) {
// Pick an appropriate FieldElement to represent this element.
// Only hard when dealing with a synthetic Field.
if (getter != null && setter == null) {
f = getterElement.variable;
} else if (getter == null && setter != null) {
f = setterElement.variable;
} else /* getter != null && setter != null */ {
// In cases where a Field is composed of two Accessors defined in
// different places in the inheritance chain, there are two FieldElements
// for this single Field we're trying to compose. Pick the one closest
// to this class on the inheritance chain.
if ((setter.enclosingElement as Class)
.isInheritingFrom(getter.enclosingElement)) {
f = setterElement.variable;
} else {
f = getterElement.variable;
}
}
}
Field field;
if ((getter == null || getter.isInherited) &&
(setter == null || setter.isInherited)) {
// Field is 100% inherited.
field = new ModelElement.from(f, library, packageGraph,
enclosingClass: this, getter: getter, setter: setter);
} else {
// Field is <100% inherited (could be half-inherited).
// TODO(jcollins-g): Navigation is probably still confusing for
// half-inherited fields when traversing the inheritance tree. Make
// this better, somehow.
field = new ModelElement.from(f, library, packageGraph,
getter: getter, setter: setter);
}
_fields.add(field);
}
ClassElement get _cls => (element as ClassElement);
List<Method> get _methods {
if (_allMethods != null) return _allMethods;
_allMethods = _cls.methods.map((e) {
return new ModelElement.from(e, library, packageGraph) as Method;
}).toList(growable: false)
..sort(byName);
return _allMethods;
}
List<TypeParameter> _typeParameters;
// a stronger hash?
@override
List<TypeParameter> get typeParameters {
if (_typeParameters == null) {
_typeParameters = _cls.typeParameters.map((f) {
var lib = new Library(f.enclosingElement.library, packageGraph);
return new ModelElement.from(f, lib, packageGraph) as TypeParameter;
}).toList();
}
return _typeParameters;
}
@override
bool operator ==(o) =>
o is Class &&
name == o.name &&
o.library.name == library.name &&
o.library.package.name == library.package.name;
}
class Constructor extends ModelElement
with TypeParameters
implements EnclosedElement {
Constructor(
ConstructorElement element, Library library, PackageGraph packageGraph)
: super(element, library, packageGraph, null);
@override
// TODO(jcollins-g): Revisit this when dart-lang/sdk#31517 is implemented.
List<TypeParameter> get typeParameters =>
(enclosingElement as Class).typeParameters;
@override
ModelElement get enclosingElement => new ModelElement.from(
_constructor.enclosingElement, library, packageGraph);
String get fullKind {
if (isConst) return 'const $kind';
if (isFactory) return 'factory $kind';
return kind;
}
@override
String get fullyQualifiedName => '${library.name}.$name';
@override
String get href {
if (!identical(canonicalModelElement, this))
return canonicalModelElement?.href;
assert(canonicalLibrary != null);
assert(canonicalLibrary == library);
return '${package.baseHref}${enclosingElement.library.dirName}/${enclosingElement.name}/$name.html';
}
@override
bool get isConst => _constructor.isConst;
bool get isFactory => _constructor.isFactory;
@override
String get kind => 'constructor';
@override
DefinedElementType get modelType => super.modelType;
String _name;
@override
String get name {
if (_name == null) {
String constructorName = element.name;
if (constructorName.isEmpty) {
_name = enclosingElement.name;
} else {
_name = '${enclosingElement.name}.$constructorName';
}
}
return _name;
}
String _nameWithGenerics;
@override
String get nameWithGenerics {
if (_nameWithGenerics == null) {
String constructorName = element.name;
if (constructorName.isEmpty) {
_nameWithGenerics = '${enclosingElement.name}${genericParameters}';
} else {
_nameWithGenerics =
'${enclosingElement.name}${genericParameters}.$constructorName';
}
}
return _nameWithGenerics;
}
String get shortName {
if (name.contains('.')) {
return name.substring(_constructor.enclosingElement.name.length + 1);
} else {
return name;
}
}
ConstructorElement get _constructor => (element as ConstructorElement);
}
/// Bridges the gap between model elements and packages,
/// both of which have documentation.
abstract class Documentable extends Nameable {
String get documentation;
String get documentationAsHtml;
bool get hasDocumentation;
bool get hasExtendedDocumentation;
String get oneLineDoc;
PackageGraph get packageGraph;
bool get isDocumented;
DartdocOptionContext get config;
}
/// Mixin implementing dartdoc categorization for ModelElements.
abstract class Categorization implements ModelElement {
@override
String _buildDocumentationAddition(String rawDocs) =>
_stripAndSetDartdocCategories(rawDocs);
/// Parse {@category ...} and related information in API comments, stripping
/// out that information from the given comments and returning the stripped
/// version.
String _stripAndSetDartdocCategories(String rawDocs) {
Set<String> _categorySet = new Set();
Set<String> _subCategorySet = new Set();
_hasCategorization = false;
rawDocs = rawDocs.replaceAllMapped(categoryRegexp, (match) {
_hasCategorization = true;
switch (match[1]) {
case 'category':
case 'api':
_categorySet.add(match[2].trim());
break;
case 'subCategory':
_subCategorySet.add(match[2].trim());
break;
case 'image':
_image = match[2].trim();
break;
case 'samples':
_samples = match[2].trim();
break;
}
return '';
});
if (_categorySet.isEmpty) {
// All objects are in the default category if not specified.
_categorySet.add(null);
}
if (_subCategorySet.isEmpty) {
// All objects are in the default subcategory if not specified.
_subCategorySet.add(null);
}
_categoryNames = _categorySet.toList()..sort();
_subCategoryNames = _subCategorySet.toList()..sort();
_image ??= '';
_samples ??= '';
return rawDocs;
}
bool get hasSubCategoryNames =>
subCategoryNames.length > 1 || subCategoryNames.first != null;
List<String> _subCategoryNames;
/// Either a set of strings containing all declared subcategories for this symbol,
/// or a set containing Null if none were declared.
List<String> get subCategoryNames {
// TODO(jcollins-g): avoid side-effect dependency
if (_subCategoryNames == null) documentationLocal;
return _subCategoryNames;
}
@override
bool get hasCategoryNames =>
categoryNames.length > 1 || categoryNames.first != null;
List<String> _categoryNames;
/// Either a set of strings containing all declared categories for this symbol,
/// or a set containing Null if none were declared.
List<String> get categoryNames {
// TODO(jcollins-g): avoid side-effect dependency
if (_categoryNames == null) documentationLocal;
return _categoryNames;
}
bool get hasImage => image.isNotEmpty;
String _image;
/// Either a URI to a defined image, or the empty string if none
/// was declared.
String get image {
// TODO(jcollins-g): avoid side-effect dependency
if (_image == null) documentationLocal;
return _image;
}
bool get hasSamples => samples.isNotEmpty;
String _samples;
/// Either a URI to documentation with samples, or the empty string if none
/// was declared.
String get samples {
// TODO(jcollins-g): avoid side-effect dependency
if (_samples == null) documentationLocal;
return _samples;
}
bool _hasCategorization;
Iterable<Category> _categories;
Iterable<Category> get categories {
if (_categories == null) {
_categories = categoryNames
.map((n) => package.nameToCategory[n])
.where((c) => c != null)
.toList()
..sort();
}
return _categories;
}
Iterable<Category> get displayedCategories {
if (config.showUndocumentedCategories) return categories;
return categories.where((c) => c.isDocumented);
}
/// True if categories, subcategories, a documentation icon, or samples were
/// declared.
bool get hasCategorization {
if (_hasCategorization == null) documentationLocal;
return _hasCategorization;
}
}
/// A stripped down [CommentReference] containing only that information needed
/// for Dartdoc. Drops link to the [CommentReference] after construction.
class ModelCommentReference {
final String name;
final Element staticElement;
ModelCommentReference(CommentReference ref)
: name = ref.identifier.name,
staticElement = ref.identifier.staticElement;
}
/// Stripped down information derived from [AstNode] containing only information
/// needed for Dartdoc. Drops link to the [AstNode] after construction.
class ModelNode {
final List<ModelCommentReference> commentRefs;
final Element element;
final int _sourceOffset;
final int _sourceEnd;
ModelNode(AstNode sourceNode, this.element)
: _sourceOffset = sourceNode?.offset,
_sourceEnd = sourceNode?.end,
commentRefs = _commentRefsFor(sourceNode);
static List<ModelCommentReference> _commentRefsFor(AstNode node) {
if (node is AnnotatedNode &&
node?.documentationComment?.references != null) {
return node.documentationComment.references
.map((c) => ModelCommentReference(c))
.toList(growable: false);
}
return null;
}
String get sourceCode {
String contents = getFileContentsFor(element);
if (_sourceOffset != null) {
// Find the start of the line, so that we can line up all the indents.
int i = _sourceOffset;
while (i > 0) {
i -= 1;
if (contents[i] == '\n' || contents[i] == '\r') {
i += 1;
break;
}
}
// Trim the common indent from the source snippet.
var start = _sourceOffset - (_sourceOffset - i);
String source = contents.substring(start, _sourceEnd);
source = const HtmlEscape().convert(source);
source = stripIndentFromSource(source);
source = stripDartdocCommentsFromSource(source);
return source.trim();
} else {
return '';
}
}
}
/// Classes extending this class have canonicalization support in Dartdoc.
abstract class Canonicalization implements Locatable, Documentable {
bool get isCanonical;
Library get canonicalLibrary;
List<ModelCommentReference> _commentRefs;
List<ModelCommentReference> get commentRefs => _commentRefs;
/// Pieces of the location split by [locationSplitter] (removing package: and
/// slashes).
Set<String> get locationPieces;
List<ScoredCandidate> scoreCanonicalCandidates(List<Library> libraries) {
return libraries.map((l) => scoreElementWithLibrary(l)).toList()..sort();
}
ScoredCandidate scoreElementWithLibrary(Library lib) {
ScoredCandidate scoredCandidate = new ScoredCandidate(this, lib);
Iterable<String> resplit(Set<String> items) sync* {
for (String item in items) {
for (String subItem in item.split('_')) {
yield subItem;
}
}
}
// Large boost for @canonicalFor, essentially overriding all other concerns.
if (lib.canonicalFor.contains(fullyQualifiedName)) {
scoredCandidate.alterScore(5.0, 'marked @canonicalFor');
}
// Penalty for deprecated libraries.
if (lib.isDeprecated) scoredCandidate.alterScore(-1.0, 'is deprecated');
// Give a big boost if the library has the package name embedded in it.
if (lib.package.namePieces.intersection(lib.namePieces).length > 0) {
scoredCandidate.alterScore(1.0, 'embeds package name');
}
// Give a tiny boost for libraries with long names, assuming they're
// more specific (and therefore more likely to be the owner of this symbol).
scoredCandidate.alterScore(.01 * lib.namePieces.length, 'name is long');
// If we don't know the location of this element, return our best guess.
// TODO(jcollins-g): is that even possible?
assert(!locationPieces.isEmpty);
if (locationPieces.isEmpty) return scoredCandidate;
// The more pieces we have of the location in our library name, the more we should boost our score.
scoredCandidate.alterScore(
lib.namePieces.intersection(locationPieces).length.toDouble() /
locationPieces.length.toDouble(),
'element location shares parts with name');
// If pieces of location at least start with elements of our library name, boost the score a little bit.
double scoreBoost = 0.0;
for (String piece in resplit(locationPieces)) {
for (String namePiece in lib.namePieces) {
if (piece.startsWith(namePiece)) {
scoreBoost += 0.001;
}
}
}
scoredCandidate.alterScore(
scoreBoost, 'element location parts start with parts of name');
return scoredCandidate;
}
}
class Dynamic extends ModelElement {
Dynamic(Element element, PackageGraph packageGraph)
: super(element, null, packageGraph, null);
/// [dynamic] is not a real object, and so we can't document it, so there
/// can be nothing canonical for it.
@override
ModelElement get canonicalModelElement => null;
@override
ModelElement get enclosingElement => throw new UnsupportedError('');
/// And similiarly, even if someone references it directly it can have
/// no hyperlink.
@override
String get href => null;
@override
String get kind => 'dynamic';
@override
String get linkedName => 'dynamic';
}
/// An element that is enclosed by some other element.
///
/// Libraries are not enclosed.
abstract class EnclosedElement {
ModelElement get enclosingElement;
}
class Enum extends Class {
Enum(ClassElement element, Library library, PackageGraph packageGraph)
: super(element, library, packageGraph);
List<EnumField> _instanceProperties;
@override
List<EnumField> get instanceProperties {
if (_instanceProperties == null) {
_instanceProperties = super
.instanceProperties
.map((Field p) => new ModelElement.from(
p.element, p.library, p.packageGraph,
getter: p.getter, setter: p.setter) as EnumField)
.toList(growable: false);
}
return _instanceProperties;
}
@override
String get kind => 'enum';
}
/// Enum's fields are virtual, so we do a little work to create
/// usable values for the docs.
class EnumField extends Field {
int _index;
EnumField(FieldElement element, Library library, PackageGraph packageGraph,
Accessor getter, Accessor setter)
: super(element, library, packageGraph, getter, setter);
EnumField.forConstant(this._index, FieldElement element, Library library,
PackageGraph packageGraph, Accessor getter)
: super(element, library, packageGraph, getter, null);
@override
String get constantValueBase {
if (name == 'values') {
return 'const List&lt;<wbr><span class="type-parameter">${_field.enclosingElement.name}</span>&gt;';
} else {
return 'const ${_field.enclosingElement.name}($_index)';
}
}
@override
List<ModelElement> get documentationFrom {
if (name == 'values' && name == 'index') return [this];
return super.documentationFrom;
}
@override
String get documentation {
if (name == 'values') {
return 'A constant List of the values in this enum, in order of their declaration.';
} else if (name == 'index') {
return 'The integer index of this enum.';
} else {
return super.documentation;
}
}
@override
String get href {
if (!identical(canonicalModelElement, this))
return canonicalModelElement?.href;
assert(!(canonicalLibrary == null || canonicalEnclosingElement == null));
assert(canonicalLibrary == library);
assert(canonicalEnclosingElement == enclosingElement);
return '${package.baseHref}${enclosingElement.library.dirName}/${(enclosingElement as Class).fileName}';
}
@override
String get linkedName => name;
@override
bool get isCanonical {
if (name == 'index') return false;
// If this is something inherited from Object, e.g. hashCode, let the
// normal rules apply.
if (_index == null) {
return super.isCanonical;
}
// TODO(jcollins-g): We don't actually document this as a separate entity;
// do that or change this to false and deal with the
// consequences.
return true;
}
@override
String get oneLineDoc => documentationAsHtml;
@override
Inheritable get overriddenElement => null;
}
class Field extends ModelElement
with GetterSetterCombo, Inheritable
implements EnclosedElement {
bool _isInherited = false;
Class _enclosingClass;
@override
final InheritableAccessor getter;
@override
final InheritableAccessor setter;
Field(FieldElement element, Library library, PackageGraph packageGraph,
this.getter, this.setter)
: super(element, library, packageGraph, null) {
assert(getter != null || setter != null);
if (getter != null) getter.enclosingCombo = this;
if (setter != null) setter.enclosingCombo = this;
_setModelType();
}
factory Field.inherited(
FieldElement element,
Class enclosingClass,
Library library,
PackageGraph packageGraph,
Accessor getter,
Accessor setter) {
Field newField = new Field(element, library, packageGraph, getter, setter);
newField._isInherited = true;
newField._enclosingClass = enclosingClass;
// Can't set _isInherited to true if this is the defining element, because
// that would mean it isn't inherited.
assert(newField.enclosingElement != newField.definingEnclosingElement);
return newField;
}
@override
String get documentation {
// Verify that hasSetter and hasGetterNoSetter are mutually exclusive,
// to prevent displaying more or less than one summary.
if (isPublic) {
Set<bool> assertCheck = new Set()
..addAll([hasPublicSetter, hasPublicGetterNoSetter]);
assert(assertCheck.containsAll([true, false]));
}
documentationFrom;
return super.documentation;
}
@override
ModelElement get enclosingElement {
if (_enclosingClass == null) {
_enclosingClass =
new ModelElement.from(_field.enclosingElement, library, packageGraph);
}
return _enclosingClass;
}
@override
String get href {
if (!identical(canonicalModelElement, this))
return canonicalModelElement?.href;
assert(canonicalLibrary != null);
assert(canonicalEnclosingElement == enclosingElement);
assert(canonicalLibrary == library);
return '${package.baseHref}${enclosingElement.library.dirName}/${enclosingElement.name}/$fileName';
}
@override
bool get isConst => _field.isConst;
/// Returns true if the FieldElement is covariant, or if the first parameter
/// for the setter is covariant.
@override
bool get isCovariant =>
setter?.isCovariant == true || (_field as FieldElementImpl).isCovariant;
@override
bool get isFinal {
/// isFinal returns true for the field even if it has an explicit getter
/// (which means we should not document it as "final").
if (hasExplicitGetter) return false;
return _field.isFinal;
}
@override
bool get isInherited => _isInherited;
@override
String get kind => isConst ? 'constant' : 'property';
String get typeName => kind;
@override
List<String> get annotations {
List<String> all_annotations = new List<String>();
all_annotations.addAll(super.annotations);
if (element is PropertyInducingElement) {
var pie = element as PropertyInducingElement;
all_annotations.addAll(annotationsFromMetadata(pie.getter?.metadata));
all_annotations.addAll(annotationsFromMetadata(pie.setter?.metadata));
}
return all_annotations.toList(growable: false);
}
@override
Set<String> get features {
Set<String> allFeatures = _baseFeatures()..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');
} else {
allFeatures.remove('inherited');
if (getter.isInherited) allFeatures.add('inherited-getter');
if (setter.isInherited) allFeatures.add('inherited-setter');
}
if (getter.isOverride && setter.isOverride) {
allFeatures.add('override');
} else {
allFeatures.remove('override');
if (getter.isOverride) allFeatures.add('override-getter');
if (setter.isOverride) allFeatures.add('override-setter');
}
} else {
if (isInherited) allFeatures.add('inherited');
if (isOverride) allFeatures.add('override');
}
return allFeatures;
}
@override
String _computeDocumentationComment() {
String docs = getterSetterDocumentationComment;
if (docs.isEmpty) return _field.documentationComment;
return docs;
}
FieldElement get _field => (element as FieldElement);
@override
String get fileName => isConst ? '$name-constant.html' : '$name.html';
@override
String get sourceCode {
if (_sourceCode == null) {
// We could use a set to figure the dupes out, but that would lose ordering.
String fieldSourceCode = modelNode.sourceCode ?? '';
String getterSourceCode = getter?.sourceCode ?? '';
String setterSourceCode = setter?.sourceCode ?? '';
StringBuffer buffer = new StringBuffer();
if (fieldSourceCode.isNotEmpty) {
buffer.write(fieldSourceCode);
}
if (buffer.isNotEmpty) buffer.write('\n\n');
if (fieldSourceCode != getterSourceCode) {
if (getterSourceCode != setterSourceCode) {
buffer.write(getterSourceCode);
if (buffer.isNotEmpty) buffer.write('\n\n');
}
}
if (fieldSourceCode != setterSourceCode) {
buffer.write(setterSourceCode);
}
_sourceCode = buffer.toString();
}
return _sourceCode;
}
void _setModelType() {
if (hasGetter) {
_modelType = getter.modelType;
}
}
@override
Inheritable get overriddenElement => null;
}
/// Mixin for top-level variables and fields (aka properties)
abstract class GetterSetterCombo implements ModelElement {
Accessor get getter;
Iterable<Accessor> get allAccessors sync* {
for (Accessor a in [getter, setter]) {
if (a != null) yield a;
}
}
Set<String> get comboFeatures {
Set<String> allFeatures = new Set();
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');
if (isCovariant) allFeatures.add('covariant');
return allFeatures;
}
bool get isCovariant => (hasSetter && setter.isCovariant);
@override
ModelElement enclosingElement;
bool get isInherited;
Expression get constantInitializer =>
(element as ConstVariableElement).constantInitializer;
String linkifyConstantValue(String original) {
if (constantInitializer is! InstanceCreationExpression) return original;
String constructorName = (constantInitializer as InstanceCreationExpression)
.constructorName
.toString();
Element staticElement =
(constantInitializer as InstanceCreationExpression).staticElement;
Constructor target =
new ModelElement.fromElement(staticElement, packageGraph);
Class targetClass = target.enclosingElement;
// TODO(jcollins-g): this logic really should be integrated into Constructor,
// but that's not trivial because of linkedName's usage.
if (targetClass.name == target.name) {
return original.replaceAll(constructorName, "${target.linkedName}");
}
return original.replaceAll("${targetClass.name}.${target.name}",
"${targetClass.linkedName}.${target.linkedName}");
}
String _buildConstantValueBase() {
String result = constantInitializer?.toString() ?? '';
return const HtmlEscape(HtmlEscapeMode.unknown).convert(result);
}
String get constantValue => linkifyConstantValue(constantValueBase);
String get constantValueTruncated =>
linkifyConstantValue(truncateString(constantValueBase, 200));
String _constantValueBase;
String get constantValueBase =>
_constantValueBase ??= _buildConstantValueBase();
/// Returns true if both accessors are synthetic.
bool get hasSyntheticAccessors {
if ((hasPublicGetter && getter.isSynthetic) ||
(hasPublicSetter && setter.isSynthetic)) {
return true;
}
return false;
}
bool get hasPublicGetter => hasGetter && getter.isPublic;
bool get hasPublicSetter => hasSetter && setter.isPublic;
@override
bool get isPublic => hasPublicGetter || hasPublicSetter;
@override
List<ModelElement> get documentationFrom {
if (_documentationFrom == null) {
_documentationFrom = [];
if (hasPublicGetter) {
_documentationFrom.addAll(getter.documentationFrom);
} else if (hasPublicSetter) {
_documentationFrom.addAll(setter.documentationFrom);
}
if (_documentationFrom.length == 0 ||
_documentationFrom.every((e) => e.documentation == ''))
_documentationFrom = computeDocumentationFrom;
}
return _documentationFrom;
}
bool get hasAccessorsWithDocs => (hasPublicGetter &&
!getter.isSynthetic &&
getter.documentation.isNotEmpty ||
hasPublicSetter &&
!setter.isSynthetic &&
setter.documentation.isNotEmpty);
bool get getterSetterBothAvailable => (hasPublicGetter &&
getter.documentation.isNotEmpty &&
hasPublicSetter &&
setter.documentation.isNotEmpty);
@override
String get oneLineDoc {
if (_oneLineDoc == null) {
if (!hasAccessorsWithDocs) {
_oneLineDoc = computeOneLineDoc();
} else {
StringBuffer buffer = new StringBuffer();
if (hasPublicGetter && getter.oneLineDoc.isNotEmpty) {
buffer.write('${getter.oneLineDoc}');
}
if (hasPublicSetter && setter.oneLineDoc.isNotEmpty) {
buffer.write('${getterSetterBothAvailable ? "" : setter.oneLineDoc}');
}
_oneLineDoc = buffer.toString();
}
}
return _oneLineDoc;
}
String get getterSetterDocumentationComment {
var buffer = new StringBuffer();
if (hasPublicGetter && !getter.isSynthetic) {
assert(getter.documentationFrom.length == 1);
// We have to check against dropTextFrom here since documentationFrom
// doesn't yield the real elements for GetterSetterCombos.
if (!config.dropTextFrom
.contains(getter.documentationFrom.first.element.library.name)) {
String docs = getter.documentationFrom.first.documentationComment;
if (docs != null) buffer.write(docs);
}
}
if (hasPublicSetter && !setter.isSynthetic) {
assert(setter.documentationFrom.length == 1);
if (!config.dropTextFrom
.contains(setter.documentationFrom.first.element.library.name)) {
String docs = setter.documentationFrom.first.documentationComment;
if (docs != null) {
if (buffer.isNotEmpty) buffer.write('\n\n');
buffer.write(docs);
}
}
}
return buffer.toString();
}
String get linkedReturnType {
if (hasGetter) {
return getter.linkedReturnType;
} else {
return setter.linkedParamsNoMetadataOrNames;
}
}
@override
bool get canHaveParameters => hasSetter;
@override
List<Parameter> get parameters => setter.parameters;
@override
String get linkedParamsNoMetadata {
if (hasSetter) return setter.linkedParamsNoMetadata;
return null;
}
bool get hasExplicitGetter => hasPublicGetter && !getter.isSynthetic;
bool get hasExplicitSetter => hasPublicSetter && !setter.isSynthetic;
bool get hasImplicitSetter => hasPublicSetter && setter.isSynthetic;
bool get hasImplicitGetter => hasPublicGetter && getter.isSynthetic;
bool get hasGetter => getter != null;
bool get hasNoGetterSetter => !hasGetterOrSetter;
bool get hasGetterOrSetter => hasExplicitGetter || hasExplicitSetter;
bool get hasSetter => setter != null;
bool get hasPublicGetterNoSetter => (hasPublicGetter && !hasPublicSetter);
String get arrow {
// →
if (readOnly) return r'&#8594;';
// ←
if (writeOnly) return r'&#8592;';
// ↔
if (readWrite) return r'&#8596;';
// A GetterSetterCombo should always be one of readOnly, writeOnly,
// or readWrite (if documented).
assert(!isPublic);
return null;
}
bool get readOnly => hasPublicGetter && !hasPublicSetter;
bool get readWrite => hasPublicGetter && hasPublicSetter;
bool get writeOnly => hasPublicSetter && !hasPublicGetter;
Accessor get setter;
}
/// Find all hashable children of a given element that are defined in the
/// [LibraryElement] given at initialization.
class _HashableChildLibraryElementVisitor
extends GeneralizingElementVisitor<void> {
final void Function(Element) libraryProcessor;
_HashableChildLibraryElementVisitor(this.libraryProcessor);
@override
void visitElement(Element element) {
libraryProcessor(element);
super.visitElement(element);
return null;
}
@override
void visitExportElement(ExportElement element) {
// [ExportElement]s are not always hashable; skip them.
return null;
}
@override
void visitImportElement(ImportElement element) {
// [ImportElement]s are not always hashable; skip them.
return null;
}
@override
void visitParameterElement(ParameterElement element) {
// [ParameterElement]s without names do not provide sufficiently distinct
// hashes / comparison, so just skip them all. (dart-lang/sdk#30146)
return null;
}
}
class Library extends ModelElement with Categorization, TopLevelContainer {
List<TopLevelVariable> _variables;
Namespace _exportedNamespace;
String _name;
factory Library(LibraryElement element, PackageGraph packageGraph) {
return packageGraph.findButDoNotCreateLibraryFor(element);
}
Library._(ResolvedLibraryResult libraryResult, PackageGraph packageGraph,
this._package)
: super(libraryResult.element, null, packageGraph, null) {
if (element == null) throw new ArgumentError.notNull('element');
// Initialize [packageGraph]'s cache of ModelNodes for relevant
// elements in this library.
Map<String, CompilationUnit> _compilationUnitMap = new Map();
_compilationUnitMap.addEntries(libraryResult.units
.map((ResolvedUnitResult u) => new MapEntry(u.path, u.unit)));
_HashableChildLibraryElementVisitor((Element e) =>
packageGraph._populateModelNodeFor(e, _compilationUnitMap))
.visitElement(element);
_exportedNamespace =
new NamespaceBuilder().createExportNamespaceForLibrary(element);
_package._allLibraries.add(this);
}
List<String> _allOriginalModelElementNames;
final Package _package;
@override
Package get package {
// Everything must be in a package. TODO(jcollins-g): Support other things
// that look like packages.
assert(_package != null);
return _package;
}
/// [allModelElements] resolved to their original names.
///
/// A collection of [ModelElement.fullyQualifiedName]s for [ModelElement]s
/// documented with this library, but these ModelElements and names correspond
/// to the defining library where each originally came from with respect
/// to inheritance and reexporting. Most useful for error reporting.
Iterable<String> get allOriginalModelElementNames {
if (_allOriginalModelElementNames == null) {
_allOriginalModelElementNames = allModelElements.map((e) {
Accessor getter;
Accessor setter;
if (e is GetterSetterCombo) {
if (e.hasGetter) {
getter =
new ModelElement.fromElement(e.getter.element, packageGraph);
}
if (e.hasSetter) {
setter =
new ModelElement.fromElement(e.setter.element, packageGraph);
}
}
return new ModelElement.from(
e.element,
packageGraph.findButDoNotCreateLibraryFor(e.element),
packageGraph,
getter: getter,
setter: setter)
.fullyQualifiedName;
}).toList();
}
return _allOriginalModelElementNames;
}
List<Class> get allClasses => _allClasses;
@override
Iterable<Class> get classes {
return _allClasses
.where((c) => !c.isErrorOrException)
.toList(growable: false);
}
SdkLibrary get sdkLib {
if (packageGraph.sdkLibrarySources.containsKey(element.librarySource)) {
return packageGraph.sdkLibrarySources[element.librarySource];
}
return null;
}
@override
bool get isPublic {
if (!super.isPublic) return false;
if (sdkLib != null && (sdkLib.isInternal || !sdkLib.isDocumented)) {
return false;
}
if (config.isLibraryExcluded(name) ||
config.isLibraryExcluded(element.librarySource.uri.toString()))
return false;
return true;
}
/// A special case where the SDK has defined that we should not document
/// this library. This is implemented by tweaking canonicalization so
/// even though the library is public and part of the Package's list,
/// we don't count it as a candidate for canonicalization.
bool get isSdkUndocumented => (sdkLib != null && !sdkLib.isDocumented);
@override
Iterable<TopLevelVariable> get constants {
if (_constants == null) {
// _getVariables() is already sorted.
_constants =
_getVariables().where((v) => v.isConst).toList(growable: false);
}
return _constants;
}
Set<Library> _packageImportedExportedLibraries;
/// Returns all libraries either imported by or exported by any public library
/// this library's package. (Not [PackageGraph], but sharing a package name).
///
/// Note: will still contain non-public libraries because those can be
/// imported or exported.
// TODO(jcollins-g): move this to [Package] once it really knows about
// more than one package.
Set<Library> get packageImportedExportedLibraries {
if (_packageImportedExportedLibraries == null) {
_packageImportedExportedLibraries = new Set();
packageGraph.publicLibraries
.where((l) => l.packageName == packageName)
.forEach((l) {
_packageImportedExportedLibraries.addAll(l.importedExportedLibraries);
});
}
return _packageImportedExportedLibraries;
}
Set<Library> _importedExportedLibraries;
/// Returns all libraries either imported by or exported by this library,
/// recursively.
Set<Library> get importedExportedLibraries {
if (_importedExportedLibraries == null) {
_importedExportedLibraries = new Set();
Set<LibraryElement> importedExportedLibraryElements = new Set();
importedExportedLibraryElements
.addAll((element as LibraryElement).importedLibraries);
importedExportedLibraryElements
.addAll((element as LibraryElement).exportedLibraries);
for (LibraryElement l in importedExportedLibraryElements) {
Library lib = new ModelElement.from(l, library, packageGraph);
_importedExportedLibraries.add(lib);
_importedExportedLibraries.addAll(lib.importedExportedLibraries);
}
}
return _importedExportedLibraries;
}
String _dirName;
String get dirName {
if (_dirName == null) {
_dirName = name;
if (isAnonymous) {
_dirName = nameFromPath;
}
_dirName = _dirName.replaceAll(':', '-').replaceAll('/', '_');
}
return _dirName;
}
Set<String> _canonicalFor;
Set<String> get canonicalFor {
if (_canonicalFor == null) {
// TODO(jcollins-g): restructure to avoid using side effects.
documentation;
}
return _canonicalFor;
}
/// Hide canonicalFor from doc while leaving a note to ourselves to
/// help with ambiguous canonicalization determination.
///
/// Example:
/// {@canonicalFor libname.ClassName}
String _setCanonicalFor(String rawDocs) {
if (_canonicalFor == null) {
_canonicalFor = new Set();
}
Set<String> notFoundInAllModelElements = new Set();
final canonicalRegExp = new RegExp(r'{@canonicalFor\s([^}]+)}');
rawDocs = rawDocs.replaceAllMapped(canonicalRegExp, (Match match) {
canonicalFor.add(match.group(1));
notFoundInAllModelElements.add(match.group(1));
return '';
});
if (notFoundInAllModelElements.isNotEmpty) {
notFoundInAllModelElements.removeAll(allOriginalModelElementNames);
}
for (String notFound in notFoundInAllModelElements) {
warn(PackageWarning.ignoredCanonicalFor, message: notFound);
}
return rawDocs;
}
String _libraryDocs;
@override
String get documentation {
if (_libraryDocs == null) {
_libraryDocs = _setCanonicalFor(super.documentation);
}
return _libraryDocs;
}
/// Libraries are not enclosed by anything.
@override
ModelElement get enclosingElement => null;
@override
List<Enum> get enums {
if (_enums != null) return _enums;
List<ClassElement> enumClasses = [];
enumClasses.addAll(_exportedNamespace.definedNames.values
.where((e) => e is ClassElement)
.cast<ClassElement>()
.where((element) => element.isEnum));
_enums = enumClasses
.map((e) => new ModelElement.from(e, this, packageGraph) as Enum)
.toList(growable: false)
..sort(byName);
return _enums;
}
@override
List<Mixin> get mixins {
if (_mixins != null) return _mixins;
/// Can not be [MixinElementImpl] because [ClassHandle]s are sometimes
/// returned from _exportedNamespace.
List<ClassElement> mixinClasses = [];
mixinClasses.addAll(_exportedNamespace.definedNames.values
.whereType<ClassElement>()
.where((ClassElement c) => c.isMixin));
_mixins = mixinClasses
.map((e) => new ModelElement.from(e, this, packageGraph) as Mixin)
.toList(growable: false)
..sort(byName);
return _mixins;
}
@override
List<Class> get exceptions {
return _allClasses
.where((c) => c.isErrorOrException)
.toList(growable: false)
..sort(byName);
}
@override
String get fileName => '$dirName-library.html';
@override
List<ModelFunction> get functions {
if (_functions != null) return _functions;
Set<FunctionElement> elements = new Set();
elements.addAll(_libraryElement.definingCompilationUnit.functions);
for (CompilationUnitElement cu in _libraryElement.parts) {
elements.addAll(cu.functions);
}
elements.addAll(_exportedNamespace.definedNames.values
.where((e) => e is FunctionElement)
.cast<FunctionElement>());
_functions = elements.map((e) {
return new ModelElement.from(e, this, packageGraph) as ModelFunction;
}).toList(growable: false)
..sort(byName);
return _functions;
}
@override
String get href {
if (!identical(canonicalModelElement, this))
return canonicalModelElement?.href;
return '${package.baseHref}${library.dirName}/$fileName';
}
InheritanceManager _inheritanceManager;
InheritanceManager get inheritanceManager {
if (_inheritanceManager == null) {
_inheritanceManager = new InheritanceManager(element);
}
return _inheritanceManager;
}
bool get isAnonymous => element.name == null || element.name.isEmpty;
@override
String get kind => 'library';
@override
Library get library => this;
@override
String get name {
if (_name == null) {
_name = getLibraryName(element);
}
return _name;
}
String _nameFromPath;
/// Generate a name for this library based on its location.
///
/// nameFromPath provides filename collision-proofing for anonymous libraries
/// by incorporating more from the location of the anonymous library into
/// the name calculation. Simple cases (such as an anonymous library in
/// 'lib') are the same, but this will include slashes and possibly colons
/// for anonymous libraries in subdirectories or other packages.
String get nameFromPath {
if (_nameFromPath == null) {
_nameFromPath = getNameFromPath(element, packageGraph.driver, package);
}
return _nameFromPath;
}
/// The real package, as opposed to the package we are documenting it with,
/// [PackageGraph.name]
String get packageName => packageMeta?.name ?? '';
/// The real packageMeta, as opposed to the package we are documenting with.
PackageMeta _packageMeta;
PackageMeta get packageMeta {
if (_packageMeta == null) {
_packageMeta = new PackageMeta.fromElement(element, config);
}
return _packageMeta;
}
String get path => _libraryElement.definingCompilationUnit.name;
/// All variables ("properties") except constants.
@override
Iterable<TopLevelVariable> get properties {
if (_properties == null) {
_properties =
_getVariables().where((v) => !v.isConst).toList(growable: false);
}
return _properties;
}
@override
List<Typedef> get typedefs {
if (_typedefs != null) return _typedefs;
Set<FunctionTypeAliasElement> elements = new Set();
elements
.addAll(_libraryElement.definingCompilationUnit.functionTypeAliases);
for (CompilationUnitElement cu in _libraryElement.parts) {
elements.addAll(cu.functionTypeAliases);
}
elements.addAll(_exportedNamespace.definedNames.values
.where((e) => e is FunctionTypeAliasElement)
.cast<FunctionTypeAliasElement>());
_typedefs = elements
.map((e) => new ModelElement.from(e, this, packageGraph) as Typedef)
.toList(growable: false)
..sort(byName);
return _typedefs;
}
List<Class> get _allClasses {
if (_classes != null) return _classes;
Set<ClassElement> types = new Set();
types.addAll(_libraryElement.definingCompilationUnit.types);
for (CompilationUnitElement cu in _libraryElement.parts) {
types.addAll(cu.types);
}
for (LibraryElement le in _libraryElement.exportedLibraries) {
types.addAll(le.definingCompilationUnit.types
.where((t) => _exportedNamespace.definedNames.values.contains(t.name))
.toList());
}
types.addAll(_exportedNamespace.definedNames.values
.where((e) => e is ClassElement && !e.isMixin)
.cast<ClassElement>()
.where((element) => !element.isEnum));
_classes = types
.map((e) => new ModelElement.from(e, this, packageGraph) as Class)
.toList(growable: false)
..sort(byName);
assert(!_classes.any((Class c) => c is Mixin));
return _classes;
}
LibraryElement get _libraryElement => (element as LibraryElement);
Class getClassByName(String name) {
return _allClasses.firstWhere((it) => it.name == name, orElse: () => null);
}
bool hasInExportedNamespace(Element element) {
Element found = _exportedNamespace.get(element.name);
if (found == null) return false;
if (found == element) return true; // this checks more than just the name
// Fix for #587, comparison between elements isn't reliable on windows.
// for some reason. sigh.
return found.runtimeType == element.runtimeType &&
found.nameOffset == element.nameOffset;
}
List<TopLevelVariable> _getVariables() {
if (_variables != null) return _variables;
Set<TopLevelVariableElement> elements = new Set();
elements.addAll(_libraryElement.definingCompilationUnit.topLevelVariables);
for (CompilationUnitElement cu in _libraryElement.parts) {
elements.addAll(cu.topLevelVariables);
}
_exportedNamespace.definedNames.values.forEach((element) {
if (element is PropertyAccessorElement) {
elements.add(element.variable);
}
});
_variables = [];
for (TopLevelVariableElement element in elements) {
Accessor getter;
if (element.getter != null)
getter = new ModelElement.from(element.getter, this, packageGraph);
Accessor setter;
if (element.setter != null)
setter = new ModelElement.from(element.setter, this, packageGraph);
ModelElement me = new ModelElement.from(element, this, packageGraph,
getter: getter, setter: setter);
_variables.add(me);
}
_variables.sort(byName);
return _variables;
}
/// Reverses URIs if needed to get a package URI.
/// Not the same as [PackageGraph.name] because there we always strip all
/// path components; this function only strips the package prefix if the
/// library is part of the default package or if it is being documented
/// remotely.
static String getNameFromPath(
LibraryElement element, AnalysisDriver driver, Package package) {
String name;
if (element.source.uri.toString().startsWith('dart:')) {
name = element.source.uri.toString();
} else {
name = driver.sourceFactory.restoreUri(element.source).toString();
}
PackageMeta hidePackage;
if (package.documentedWhere == DocumentLocation.remote) {
hidePackage = package.packageMeta;
} else {
hidePackage = package.packageGraph.packageMeta;
}
if (name.startsWith('file:')) {
// restoreUri doesn't do anything for the package we're documenting.
String canonicalPackagePath =
'${pathLib.canonicalize(hidePackage.dir.path)}${pathLib.separator}lib${pathLib.separator}';
String canonicalElementPath =
pathLib.canonicalize(element.source.uri.toFilePath());
assert(canonicalElementPath.startsWith(canonicalPackagePath));
List<String> pathSegments = [hidePackage.name]..addAll(pathLib
.split(canonicalElementPath.replaceFirst(canonicalPackagePath, '')));
Uri libraryUri = new Uri(
scheme: 'package',
pathSegments: pathSegments,
);
name = libraryUri.toString();
}
String defaultPackagePrefix = 'package:$hidePackage/';
if (name.startsWith(defaultPackagePrefix)) {
name = name.substring(defaultPackagePrefix.length, name.length);
}
if (name.endsWith('.dart')) {
name = name.substring(0, name.length - '.dart'.length);
}
assert(!name.startsWith('file:'));
return name;
}
static PackageMeta getPackageMeta(Element element) {
String sourcePath = element.source.fullName;
return new PackageMeta.fromDir(
new File(pathLib.canonicalize(sourcePath)).parent);
}
static String getLibraryName(LibraryElement element) {
var source = element.source;
String name = element.name;
if (name == null || name.isEmpty) {
// handle the case of an anonymous library
name = pathLib.basename(source.fullName);
if (name.endsWith('.dart')) {
name = name.substring(0, name.length - '.dart'.length);
}