blob: 8cc9d4a3e1bd7b126f7eac4b3a9a0157779689d3 [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:convert';
import 'dart:io';
import 'package:analyzer/dart/ast/ast.dart'
show AnnotatedNode, Annotation, Declaration;
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/generated/resolver.dart'
show Namespace, NamespaceBuilder, InheritanceManager, MemberMap;
import 'package:analyzer/src/generated/source_io.dart';
import 'package:analyzer/src/generated/utilities_dart.dart' show ParameterKind;
import 'package:collection/collection.dart';
import 'package:path/path.dart' as p;
import 'package:quiver/core.dart' show hash3;
import 'config.dart';
import 'element_type.dart';
import 'line_number_cache.dart';
import 'markdown_processor.dart' show Documentation;
import 'model_utils.dart';
import 'package_meta.dart' show PackageMeta, FileContents;
import 'utils.dart' show stripComments;
final Map<Class, List<Class>> _implementors = new Map();
int byName(Nameable a, Nameable b) =>
compareAsciiLowerCaseNatural(a.name, b.name);
void _addToImplementors(Class c) {
_implementors.putIfAbsent(c, () => []);
void _checkAndAddClass(Class key, Class implClass) {
_implementors.putIfAbsent(key, () => []);
List list = _implementors[key];
if (!list.any((l) => l.element == c.element)) {
list.add(implClass);
}
}
if (!c._mixins.isEmpty) {
c._mixins.forEach((t) {
_checkAndAddClass(t.element, c);
});
}
if (c._supertype != null) {
_checkAndAddClass(c._supertype.element, c);
}
if (!c._interfaces.isEmpty) {
c._interfaces.forEach((t) {
_checkAndAddClass(t.element, c);
});
}
}
/// Getters and setters.
class Accessor extends ModelElement implements EnclosedElement {
Accessor(PropertyAccessorElement element, Library library)
: super(element, library);
@override
ModelElement get enclosingElement {
if (_accessor.enclosingElement is CompilationUnitElement) {
return package
._getLibraryFor(_accessor.enclosingElement.enclosingElement);
}
return new ModelElement.from(_accessor.enclosingElement, library);
}
@override
String get href =>
'${library.dirName}/${_accessor.enclosingElement.name}/${name}.html';
bool get isGetter => _accessor.isGetter;
@override
String get kind => 'accessor';
PropertyAccessorElement get _accessor => (element as PropertyAccessorElement);
}
class Class extends ModelElement implements EnclosedElement {
List<ElementType> _mixins;
ElementType _supertype;
List<ElementType> _interfaces;
List<Constructor> _constructors;
List<Method> _allMethods;
List<Operator> _operators;
List<Operator> _inheritedOperators;
List<Operator> _allOperators;
final List<Operator> _genPageOperators = <Operator>[];
List<Method> _inheritedMethods;
List<Method> _staticMethods;
List<Method> _instanceMethods;
List<Method> _allInstanceMethods;
final List<Method> _genPageMethods = <Method>[];
List<Field> _fields;
List<Field> _staticFields;
List<Field> _constants;
List<Field> _instanceFields;
List<Field> _inheritedProperties;
List<Field> _allInstanceProperties;
final List<Field> _genPageProperties = <Field>[];
Class(ClassElement element, Library library) : super(element, library) {
Package p = library.package;
_modelType = new ElementType(_cls.type, this);
_mixins = _cls.mixins
.map((f) {
Library lib = new Library(f.element.library, p);
ElementType t =
new ElementType(f, new ModelElement.from(f.element, lib));
bool exclude = t.element.element.isPrivate;
if (exclude) {
return null;
} else {
return t;
}
})
.where((mixin) => mixin != null)
.toList(growable: false);
if (_cls.supertype != null && _cls.supertype.element.supertype != null) {
Library lib = package._getLibraryFor(_cls.supertype.element);
_supertype = new ElementType(
_cls.supertype, new ModelElement.from(_cls.supertype.element, lib));
/* Private Superclasses should not be shown. */
var exclude = _supertype.element.element.isPrivate;
/* Hide dart2js related stuff */
exclude = exclude ||
(lib.name.startsWith("dart:") &&
_supertype.name == "NativeFieldWrapperClass2");
if (exclude) {
_supertype = null;
}
}
_interfaces = _cls.interfaces
.map((f) {
var lib = new Library(f.element.library, p);
var t = new ElementType(f, new ModelElement.from(f.element, lib));
var exclude = t.element.element.isPrivate;
if (exclude) {
return null;
} else {
return t;
}
})
.where((it) => it != null)
.toList(growable: false);
}
List<Method> get allInstanceMethods {
if (_allInstanceMethods != null) return _allInstanceMethods;
_allInstanceMethods = []
..addAll(instanceMethods)
..addAll(inheritedMethods)
..sort(byName);
return _allInstanceMethods;
}
bool get allInstanceMethodsInherited =>
instanceMethods.every((f) => f.isInherited);
List<Field> get allInstanceProperties {
if (_allInstanceProperties != null) return _allInstanceProperties;
// TODO best way to make this a fixed length list?
_allInstanceProperties = []
..addAll(instanceProperties)
..addAll(inheritedProperties)
..sort(byName);
return _allInstanceProperties;
}
bool get allInstancePropertiesInherited =>
instanceProperties.every((f) => f.isInherited);
List<Operator> get allOperators {
if (_allOperators != null) return _allOperators;
_allOperators = []
..addAll(operators)
..addAll(inheritedOperators)
..sort(byName);
return _allOperators;
}
bool get allOperatorsInherited => operators.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;
}
List<Constructor> get constructors {
if (_constructors != null) return _constructors;
_constructors = _cls.constructors.where(isPublic).map((e) {
return new Constructor(e, library);
}).toList(growable: true)..sort(byName);
return _constructors;
}
/// Returns the library that encloses this element.
ModelElement get enclosingElement => library;
String get fileName => "${name}-class.html";
String get fullkind {
if (isAbstract) return 'abstract $kind';
return kind;
}
bool get hasConstants => constants.isNotEmpty;
bool get hasConstructors => constructors.isNotEmpty;
int get hashCode => hash3(
name.hashCode, library.name.hashCode, library.package.name.hashCode);
bool get hasImplementors => implementors.isNotEmpty;
bool get hasInheritedMethods => inheritedMethods.isNotEmpty;
bool get hasInstanceMethods => instanceMethods.isNotEmpty;
bool get hasInstanceProperties => instanceProperties.isNotEmpty;
bool get hasInterfaces => interfaces.isNotEmpty;
bool get hasMethods =>
instanceMethods.isNotEmpty || inheritedMethods.isNotEmpty;
bool get hasMixins => mixins.isNotEmpty;
bool get hasModifiers =>
hasMixins ||
hasAnnotations ||
hasInterfaces ||
hasSupertype ||
hasImplementors;
bool get hasOperators =>
operators.isNotEmpty || inheritedOperators.isNotEmpty;
bool get hasProperties =>
inheritedProperties.isNotEmpty || instanceProperties.isNotEmpty;
bool get hasStaticMethods => staticMethods.isNotEmpty;
bool get hasStaticProperties => staticProperties.isNotEmpty;
bool get hasSupertype => supertype != null;
@override
String get href => '${library.dirName}/$fileName';
/// Returns all the implementors of the class specified.
List<Class> get implementors =>
_implementors[this] != null ? _implementors[this] : [];
List<Method> get inheritedMethods {
if (_inheritedMethods != null) return _inheritedMethods;
InheritanceManager manager = new InheritanceManager(element.library);
MemberMap cmap = manager.getMapOfMembersInheritedFromClasses(element);
MemberMap imap = manager.getMapOfMembersInheritedFromInterfaces(element);
// remove methods that exist on this class
_methods.forEach((method) {
cmap.remove(method.name);
imap.remove(method.name);
});
_inheritedMethods = [];
List<ExecutableElement> vs = [];
Set<String> uniqueNames = new Set();
instanceProperties.forEach((f) {
if (f._setter != null) uniqueNames.add(f._setter.name);
if (f._getter != null) uniqueNames.add(f._getter.name);
});
for (var i = 0; i < cmap.size; i++) {
// XXX: if we care about showing a hierarchy with our inherited methods,
// then don't do this
if (uniqueNames.contains(cmap.getKey(i))) continue;
uniqueNames.add(cmap.getKey(i));
vs.add(cmap.getValue(i));
}
for (var i = 0; i < imap.size; i++) {
// XXX: if we care about showing a hierarchy with our inherited methods,
// then don't do this
if (uniqueNames.contains(imap.getKey(i))) continue;
uniqueNames.add(imap.getKey(i));
vs.add(imap.getValue(i));
}
for (ExecutableElement value in vs) {
if (value != null &&
value is MethodElement &&
isPublic(value) &&
!value.isOperator &&
value.enclosingElement != null) {
if (!package.isDocumented(value.enclosingElement)) {
Method m = new Method.inherited(value, this, library);
_inheritedMethods.add(m);
_genPageMethods.add(m);
} else {
Library lib = package._getLibraryFor(value.enclosingElement);
_inheritedMethods.add(new Method.inherited(
value, new Class(value.enclosingElement, lib), lib));
}
}
}
_inheritedMethods.sort(byName);
return _inheritedMethods;
}
List<Method> get inheritedOperators {
if (_inheritedOperators != null) return _inheritedOperators;
InheritanceManager manager = new InheritanceManager(element.library);
MemberMap cmap = manager.getMapOfMembersInheritedFromClasses(element);
MemberMap imap = manager.getMapOfMembersInheritedFromInterfaces(element);
operators.forEach((operator) {
cmap.remove(operator.element.name);
imap.remove(operator.element.name);
});
_inheritedOperators = [];
Map<String, ExecutableElement> vs = {};
bool _isInheritedOperator(ExecutableElement value) {
if (value != null &&
value is MethodElement &&
!value.isPrivate &&
value.isOperator &&
value.enclosingElement != null) {
return true;
}
return false;
}
for (int i = 0; i < imap.size; i++) {
ExecutableElement value = imap.getValue(i);
if (_isInheritedOperator(value)) {
vs.putIfAbsent(value.name, () => value);
}
}
for (int i = 0; i < cmap.size; i++) {
ExecutableElement value = cmap.getValue(i);
if (_isInheritedOperator(value)) {
vs.putIfAbsent(value.name, () => value);
}
}
for (ExecutableElement value in vs.values) {
if (!package.isDocumented(value.enclosingElement)) {
Operator o = new Operator.inherited(value, this, library);
_inheritedOperators.add(o);
_genPageOperators.add(o);
} else {
Library lib = package._getLibraryFor(value.enclosingElement);
_inheritedOperators.add(new Operator.inherited(
value, new Class(value.enclosingElement, lib), lib));
}
}
_inheritedOperators.sort(byName);
return _inheritedOperators;
}
List<Field> get inheritedProperties {
if (_inheritedProperties != null) return _inheritedProperties;
InheritanceManager manager = new InheritanceManager(element.library);
MemberMap cmap = manager.getMapOfMembersInheritedFromClasses(element);
MemberMap imap = manager.getMapOfMembersInheritedFromInterfaces(element);
_inheritedProperties = [];
List<ExecutableElement> vs = [];
Set<String> uniqueNames = new Set();
instanceProperties.forEach((f) {
if (f._setter != null) uniqueNames.add(f._setter.name);
if (f._getter != null) uniqueNames.add(f._getter.name);
});
for (var i = 0; i < cmap.size; i++) {
// XXX: if we care about showing a hierarchy with our inherited methods,
// then don't do this
if (uniqueNames.contains(cmap.getKey(i))) continue;
uniqueNames.add(cmap.getKey(i));
vs.add(cmap.getValue(i));
}
for (var i = 0; i < imap.size; i++) {
// XXX: if we care about showing a hierarchy with our inherited methods,
// then don't do this
if (uniqueNames.contains(imap.getKey(i))) continue;
uniqueNames.add(imap.getKey(i));
vs.add(imap.getValue(i));
}
vs.removeWhere((it) => instanceProperties.any((i) => it.name == i.name));
for (var value in vs) {
if (value != null &&
value is PropertyAccessorElement &&
isPublic(value) &&
value.enclosingElement != null) {
// TODO: why is this here?
var e = value.variable;
if (_inheritedProperties.any((f) => f.element == e)) {
continue;
}
if (!package.isDocumented(value.enclosingElement)) {
Field f = new Field.inherited(e, this, library);
_inheritedProperties.add(f);
_genPageProperties.add(f);
} else {
Library lib = package._getLibraryFor(e.enclosingElement);
_inheritedProperties.add(
new Field.inherited(e, new Class(e.enclosingElement, lib), lib));
}
}
}
_inheritedProperties.sort(byName);
return _inheritedProperties;
}
List<Method> get instanceMethods {
if (_instanceMethods != null) return _instanceMethods;
_instanceMethods = _methods
.where((m) => !m.isStatic && !m.isOperator)
.toList(growable: false)..sort(byName);
_genPageMethods.addAll(_instanceMethods);
return _instanceMethods;
}
List<Field> get instanceProperties {
if (_instanceFields != null) return _instanceFields;
_instanceFields = _allFields
.where((f) => !f.isStatic)
.toList(growable: false)..sort(byName);
_genPageProperties.addAll(_instanceFields);
return _instanceFields;
}
List<ElementType> get interfaces => _interfaces;
bool get isAbstract => _cls.isAbstract;
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);
}
@override
String get kind => 'class';
List<Method> get methodsForPages => _genPageMethods;
// TODO: make this method smarter about hierarchies and overrides. Right
// now, we're creating a flat list. We're not paying attention to where
// these methods are actually coming from. This might turn out to be a
// problem if we want to show that info later.
List<ElementType> get mixins => _mixins;
String get nameWithGenerics {
if (!modelType.isParameterizedType) return name;
return '$name&lt;${_typeParameters.map((t) => t.name).join(', ')}&gt;';
}
List<Operator> get operators {
if (_operators != null) return _operators;
_operators = _methods.where((m) => m.isOperator).toList(growable: false)
..sort(byName);
_genPageOperators.addAll(_operators);
return _operators;
}
List<Operator> get operatorsForPages => _genPageOperators;
// TODO: make this method smarter about hierarchies and overrides. Right
// now, we're creating a flat list. We're not paying attention to where
// these methods are actually coming from. This might turn out to be a
// problem if we want to show that info later.
List<Field> get propertiesForPages => _genPageProperties;
List<Method> get staticMethods {
if (_staticMethods != null) return _staticMethods;
_staticMethods = _methods.where((m) => m.isStatic).toList(growable: false)
..sort(byName);
return _staticMethods;
}
List<Field> get staticProperties {
if (_staticFields != null) return _staticFields;
_staticFields = _allFields
.where((f) => f.isStatic)
.where((f) => !f.isConst)
.toList(growable: false)..sort(byName);
return _staticFields;
}
List<ElementType> get superChain {
List<ElementType> typeChain = [];
var parent = _supertype;
while (parent != null) {
typeChain.add(parent);
parent = (parent.element as Class)._supertype;
}
return typeChain;
}
List<ElementType> get superChainReversed => superChain.reversed.toList();
ElementType get supertype => _supertype;
List<Field> get _allFields {
if (_fields != null) return _fields;
_fields = _cls.fields
.where(isPublic)
.map((e) => new Field(e, library))
.toList(growable: false)..sort(byName);
return _fields;
}
ClassElement get _cls => (element as ClassElement);
List<Method> get _methods {
if (_allMethods != null) return _allMethods;
_allMethods = _cls.methods.where(isPublic).map((e) {
if (!e.isOperator) {
return new Method(e, library);
} else {
return new Operator(e, library);
}
}).toList(growable: false)..sort(byName);
return _allMethods;
}
// a stronger hash?
List<TypeParameter> get _typeParameters => _cls.typeParameters.map((f) {
var lib = new Library(f.enclosingElement.library, package);
return new TypeParameter(f, lib);
}).toList();
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 SourceCodeMixin
implements EnclosedElement {
Constructor(ConstructorElement element, Library library)
: super(element, library);
@override
ModelElement get enclosingElement =>
new ModelElement.from(_constructor.enclosingElement, library);
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 =>
'${library.dirName}/${_constructor.enclosingElement.name}/$name.html';
bool get isConst => _constructor.isConst;
bool get isFactory => _constructor.isFactory;
@override
String get kind => 'constructor';
@override
String get name {
String constructorName = element.name;
Class c = new ModelElement.from(element.enclosingElement, library) as Class;
if (constructorName.isEmpty) {
return c.name;
} else {
return '${c.name}.$constructorName';
}
}
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 {
String get documentation;
String get documentationAsHtml;
bool get hasDocumentation;
bool get hasMoreThanOneLineDocs;
String get oneLineDoc;
}
// TODO: how do we get rid of this class?
class Dynamic extends ModelElement {
Dynamic(Element element, Library library) : super(element, library);
ModelElement get enclosingElement => throw new UnsupportedError('');
@override
String get href => throw new StateError('dynamic should not have an href');
@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 {
List<EnumField> _constants;
Enum(ClassElement element, Library library) : super(element, library);
@override
List<EnumField> get constants {
if (_constants != null) return _constants;
// This is a hack to give 'values' an index of -1 and all other fields
// their expected indicies. https://github.com/dart-lang/dartdoc/issues/1176
var index = -1;
_constants = _cls.fields
.where(isPublic)
.where((f) => f.isConst)
.map((field) => new EnumField.forConstant(index++, field, library))
.toList(growable: false)..sort(byName);
return _constants;
}
@override
List<EnumField> get instanceProperties {
return super
.instanceProperties
.map((Field p) => new EnumField(p.element, p.library))
.toList(growable: false);
}
@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) : super(element, library);
EnumField.forConstant(this._index, FieldElement element, Library library)
: super(element, library);
@override
String get constantValue {
if (name == 'values') {
return 'const List&lt;${_field.enclosingElement.name}&gt;';
} else {
return 'const ${_field.enclosingElement.name}($_index)';
}
}
@override
String get documentation {
if (name == 'values') {
return 'A constant List of the values in this enum, in order of their declaration.';
} else {
return super.documentation;
}
}
@override
String get href =>
'${library.dirName}/${(enclosingElement as Class).fileName}';
@override
String get linkedName => name;
}
class Field extends ModelElement
with GetterSetterCombo
implements EnclosedElement {
String _constantValue;
bool _isInherited = false;
Class _enclosingClass;
Field(FieldElement element, Library library) : super(element, library) {
_setModelType();
}
Field.inherited(FieldElement element, this._enclosingClass, Library library)
: super(element, library) {
_isInherited = true;
_setModelType();
}
String get constantValue {
if (_constantValue != null) return _constantValue;
if (_field.computeNode() == null) return null;
var v = _field.computeNode().toSource();
if (v == null) return null;
var string = v.substring(v.indexOf('=') + 1, v.length).trim();
_constantValue = string.replaceAll(modelType.name, modelType.linkedName);
return _constantValue;
}
@override
ModelElement get enclosingElement {
if (_enclosingClass == null) {
_enclosingClass = new ModelElement.from(_field.enclosingElement, library);
}
return _enclosingClass;
}
bool get hasGetter => _field.getter != null;
bool get hasSetter => _field.setter != null;
@override
String get href {
if (enclosingElement is Class) {
return '${library.dirName}/${enclosingElement.name}/$_fileName';
} else if (enclosingElement is Library) {
return '${library.dirName}/$_fileName';
} else {
throw new StateError(
'$name is not in a class or library, instead it is a ${enclosingElement.element}');
}
}
bool get isConst => _field.isConst;
bool get isFinal => _field.isFinal;
bool get isInherited => _isInherited;
@override
String get kind => 'property';
String get linkedReturnType => modelType.linkedName;
bool get readOnly => hasGetter && !hasSetter;
bool get readWrite => hasGetter && hasSetter;
String get typeName => "property";
bool get writeOnly => hasSetter && !hasGetter;
@override
String get _computeDocumentationComment {
String docs = getterSetterDocumentationComment;
if (docs.isEmpty) return _field.documentationComment;
return docs;
}
FieldElement get _field => (element as FieldElement);
String get _fileName => isConst ? '$name-constant.html' : '$name.html';
PropertyAccessorElement get _getter => _field.getter;
PropertyAccessorElement get _setter => _field.setter;
void _setModelType() {
if (hasGetter) {
var t = _field.getter.returnType;
_modelType = new ElementType(t,
new ModelElement.from(t.element, package._getLibraryFor(t.element)));
} else {
var s = _field.setter.parameters.first.type;
_modelType = new ElementType(s,
new ModelElement.from(s.element, package._getLibraryFor(s.element)));
}
}
}
/// Mixin for top-level variables and fields (aka properties)
abstract class GetterSetterCombo {
Accessor get getter {
return _getter == null ? null : new ModelElement.from(_getter, library);
}
String get getterSetterDocumentationComment {
var buffer = new StringBuffer();
if (hasGetter && !_getter.isSynthetic) {
String docs = stripComments(_getter.documentationComment);
if (docs != null) buffer.write(docs);
}
if (hasSetter && !_setter.isSynthetic) {
String docs = stripComments(_setter.documentationComment);
if (docs != null) {
if (buffer.isNotEmpty) buffer.write('\n\n');
buffer.write(docs);
}
}
return buffer.toString();
}
bool get hasExplicitGetter => hasGetter && !_getter.isSynthetic;
bool get hasExplicitSetter => hasSetter && !_setter.isSynthetic;
bool get hasGetter;
bool get hasNoGetterSetter => !hasExplicitGetter && !hasExplicitSetter;
bool get hasSetter;
Library get library;
Accessor get setter {
return _setter == null ? null : new ModelElement.from(_setter, library);
}
PropertyAccessorElement get _getter;
// TODO: now that we have explicit getter and setters, we probably
// want a cleaner way to do this. Only the one-liner is using this
// now. The detail pages should be using getter and setter directly.
PropertyAccessorElement get _setter;
}
class Library extends ModelElement {
static final Map<String, Library> _libraryMap = <String, Library>{};
final Package package;
List<Class> _classes;
List<Class> _enums;
List<ModelFunction> _functions;
List<Typedef> _typeDefs;
List<TopLevelVariable> _variables;
Namespace _exportedNamespace;
String _name;
String _packageName;
factory Library(LibraryElement element, Package package) {
String key = element == null ? 'null' : element.name;
if (key.isEmpty) {
String name = element.definingCompilationUnit.name;
key = name.substring(0, name.length - '.dart'.length);
}
if (_libraryMap.containsKey(key)) {
return _libraryMap[key];
}
Library library = new Library._(element, package);
_libraryMap[key] = library;
return library;
}
Library._(LibraryElement element, this.package) : super(element, null) {
if (element == null) throw new ArgumentError.notNull('element');
_exportedNamespace =
new NamespaceBuilder().createExportNamespaceForLibrary(element);
}
List<Class> get allClasses => _allClasses;
List<Class> get classes {
return _allClasses
.where((c) => !c.isErrorOrException)
.toList(growable: false);
}
List<TopLevelVariable> get constants {
return _getVariables().where((v) => v.isConst).toList(growable: false)
..sort(byName);
}
String get dirName => name.replaceAll(':', '-');
/// Libraries are not enclosed by anything.
ModelElement get enclosingElement => null;
List<Class> get enums {
if (_enums != null) return _enums;
List<ClassElement> enumClasses = [];
enumClasses.addAll(_exportedNamespace.definedNames.values
.where((element) => element is ClassElement && element.isEnum));
_enums = enumClasses
.where(isPublic)
.map((e) => new Enum(e, this))
.toList(growable: false)..sort(byName);
return _enums;
}
List<Class> get exceptions {
return _allClasses
.where((c) => c.isErrorOrException)
.toList(growable: false)..sort(byName);
}
String get fileName => '$dirName-library.html';
List<ModelFunction> get functions {
if (_functions != null) return _functions;
Set<FunctionElement> elements = new Set();
elements.addAll(_library.definingCompilationUnit.functions);
for (CompilationUnitElement cu in _library.parts) {
elements.addAll(cu.functions);
}
elements.addAll(_exportedNamespace.definedNames.values
.where((element) => element is FunctionElement));
_functions = elements.where(isPublic).map((e) {
return new ModelFunction(e, this);
}).toList(growable: false)..sort(byName);
return _functions;
}
bool get hasClasses => classes.isNotEmpty;
bool get hasConstants => _getVariables().any((v) => v.isConst);
bool get hasEnums => enums.isNotEmpty;
bool get hasExceptions => _allClasses.any((c) => c.isErrorOrException);
bool get hasFunctions => functions.isNotEmpty;
bool get hasProperties => _getVariables().any((v) => !v.isConst);
bool get hasTypedefs => typedefs.isNotEmpty;
@override
String get href => '$dirName/$fileName';
bool get isAnonymous => element.name == null || element.name.isEmpty;
bool get isDocumented => oneLineDoc.isNotEmpty;
bool get isInSdk => _library.isInSdk;
@override
String get kind => 'library';
Library get library => this;
String get name {
if (_name != null) return _name;
// handle the case of an anonymous library
if (element.name == null || element.name.isEmpty) {
_name = _library.definingCompilationUnit.name;
if (_name.endsWith('.dart')) {
_name = _name.substring(0, _name.length - '.dart'.length);
}
} else {
_name = element.name;
}
// So, if the library is a system library, it's name is not
// dart:___, it's dart.___. Apparently the way to get to the dart:___
// name is to get source.encoding.
// This may be wrong or misleading, but developers expect the name
// of dart:____
var source = _library.definingCompilationUnit.source;
_name = source.isInSystemLibrary ? source.encoding : _name;
return _name;
}
String get packageName {
if (_packageName == null) {
String sourcePath = _library.source.fullName;
File file = new File(sourcePath);
if (file.existsSync()) {
_packageName = _getPackageName(file.parent);
if (_packageName == null) _packageName = '';
} else {
_packageName = '';
}
}
return _packageName;
}
String get path => _library.definingCompilationUnit.name;
/// All variables ("properties") except constants.
List<TopLevelVariable> get properties {
return _getVariables().where((v) => !v.isConst).toList(growable: false)
..sort(byName);
}
List<Typedef> get typedefs {
if (_typeDefs != null) return _typeDefs;
Set<FunctionTypeAliasElement> elements = new Set();
elements.addAll(_library.definingCompilationUnit.functionTypeAliases);
for (CompilationUnitElement cu in _library.parts) {
elements.addAll(cu.functionTypeAliases);
}
elements.addAll(_exportedNamespace.definedNames.values
.where((element) => element is FunctionTypeAliasElement));
elements..removeWhere(isPrivate);
_typeDefs = elements
.map((e) => new Typedef(e, this))
.toList(growable: false)..sort(byName);
return _typeDefs;
}
List<Class> get _allClasses {
if (_classes != null) return _classes;
Set<ClassElement> types = new Set();
types.addAll(_library.definingCompilationUnit.types);
for (CompilationUnitElement cu in _library.parts) {
types.addAll(cu.types);
}
for (LibraryElement le in _library.exportedLibraries) {
types.addAll(le.definingCompilationUnit.types
.where((t) => _exportedNamespace.definedNames.values.contains(t.name))
.toList());
}
types.addAll(_exportedNamespace.definedNames.values
.where((element) => element is ClassElement && !element.isEnum));
_classes = types
.where(isPublic)
.map((e) => new Class(e, this))
.toList(growable: false)..sort(byName);
return _classes;
}
LibraryElement get _library => (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(_library.definingCompilationUnit.topLevelVariables);
for (CompilationUnitElement cu in _library.parts) {
elements.addAll(cu.topLevelVariables);
}
_exportedNamespace.definedNames.values.forEach((element) {
if (element is PropertyAccessorElement) elements.add(element.variable);
});
_variables = elements
.where(isPublic)
.map((e) => new TopLevelVariable(e, this))
.toList(growable: false)..sort(byName);
return _variables;
}
static String getLibraryName(LibraryElement element) {
String name = element.name;
if (name == null || name.isEmpty) {
name = element.definingCompilationUnit.name;
name = name.substring(0, name.length - '.dart'.length);
}
return name;
}
static String _getPackageName(Directory dir) {
if (!dir.existsSync() || !dir.path.contains(Platform.pathSeparator)) {
return null;
}
File pubspec = new File(p.join(dir.path, 'pubspec.yaml'));
if (pubspec.existsSync()) {
PackageMeta meta = new PackageMeta.fromDir(dir);
return meta.name;
} else {
return _getPackageName(dir.parent);
}
}
}
class Method extends ModelElement
with SourceCodeMixin
implements EnclosedElement {
bool _isInherited = false;
Class _enclosingClass;
Method(MethodElement element, Library library) : super(element, library) {
_modelType = new ElementType(_method.type, this);
}
Method.inherited(MethodElement element, this._enclosingClass, Library library)
: super(element, library) {
_modelType = new ElementType(_method.type, this);
_isInherited = true;
}
@override
ModelElement get enclosingElement {
if (_enclosingClass == null) {
_enclosingClass =
new ModelElement.from(_method.enclosingElement, library);
}
return _enclosingClass;
}
String get fileName => "${name}.html";
@override
String get href => '${library.dirName}/${enclosingElement.name}/${fileName}';
bool get isInherited => _isInherited;
bool get isOperator => false;
@override
bool get isStatic => _method.isStatic;
@override
String get kind => 'method';
String get linkedReturnType => modelType.createLinkedReturnTypeName();
Method get overriddenElement {
ClassElement parent = element.enclosingElement;
for (InterfaceType t in getAllSupertypes(parent)) {
if (t.getMethod(element.name) != null) {
return new Method(t.getMethod(element.name), library);
}
}
return null;
}
String get typeName => 'method';
MethodElement get _method => (element as MethodElement);
}
// TODO: rename this to Property
abstract class ModelElement implements Comparable, Nameable, Documentable {
final Element element;
final Library library;
ElementType _modelType;
String _rawDocs;
Documentation __documentation;
List _parameters;
String _linkedName;
String _fullyQualifiedName;
// WARNING: putting anything into the body of this seems
// to lead to stack overflows. Need to make a registry of ModelElements
// somehow.
ModelElement(this.element, this.library);
factory ModelElement.from(Element e, Library library) {
if (e.kind == ElementKind.DYNAMIC) {
return new Dynamic(e, library);
}
// Also handles enums
if (e is ClassElement) {
return new Class(e, library);
}
if (e is FunctionElement) {
return new ModelFunction(e, library);
}
if (e is FunctionTypeAliasElement) {
return new Typedef(e, library);
}
if (e is FieldElement) {
return new Field(e, library);
}
if (e is ConstructorElement) {
return new Constructor(e, library);
}
if (e is MethodElement && e.isOperator) {
return new Operator(e, library);
}
if (e is MethodElement && !e.isOperator) {
return new Method(e, library);
}
if (e is TopLevelVariableElement) {
return new TopLevelVariable(e, library);
}
if (e is PropertyAccessorElement) {
return new Accessor(e, library);
}
if (e is TypeParameterElement) {
return new TypeParameter(e, library);
}
if (e is ParameterElement) {
return new Parameter(e, library);
}
throw "Unknown type ${e.runtimeType}";
}
List<String> get annotations {
// Check https://code.google.com/p/dart/issues/detail?id=23181
// If that is fixed, this code might get a lot easier
if (element.computeNode() != null &&
element.computeNode() is AnnotatedNode) {
return (element.computeNode() as AnnotatedNode)
.metadata
.map((Annotation a) {
var annotationString = a.toSource().substring(1); // remove the @
var e = a.element;
if (e != null && (e is ConstructorElement)) {
var me = new ModelElement.from(
e.enclosingElement, package._getLibraryFor(e.enclosingElement));
if (me.href != null) {
return annotationString.replaceAll(me.name, me.linkedName);
}
}
return annotationString;
}).toList(growable: false);
} else {
return element.metadata.map((ElementAnnotation a) {
// TODO link to the element's href
return a.element.name;
}).toList(growable: false);
}
}
bool get canHaveParameters =>
element is ExecutableElement || element is FunctionTypeAliasElement;
/// Returns the docs, stripped of their
/// leading comments syntax.
///
/// This getter will walk up the inheritance hierarchy
/// to find docs, if the current class doesn't have docs
/// for this element.
String get documentation {
if (_rawDocs != null) return _rawDocs;
_rawDocs = _computeDocumentationComment;
if (_rawDocs == null && canOverride()) {
var overrideElement = overriddenElement;
if (overrideElement != null) {
_rawDocs = overrideElement.documentation ?? '';
return _rawDocs;
}
}
_rawDocs = stripComments(_rawDocs) ?? '';
_rawDocs = _injectExamples(_rawDocs);
return _rawDocs;
}
@override
String get documentationAsHtml => _documentation.asHtml;
/// Returns the fully qualified name.
///
/// For example: libraryName.className.methodName
String get fullyQualifiedName {
return (_fullyQualifiedName ??= _buildFullyQualifiedName());
}
bool get hasAnnotations => annotations.isNotEmpty;
@override
bool get hasDocumentation =>
documentation != null && documentation.isNotEmpty;
@override
bool get hasMoreThanOneLineDocs => _documentation.hasMoreThanOneLineDocs;
bool get hasParameters => parameters.isNotEmpty;
String get href;
String get htmlId => name;
bool get isAsynchronous =>
isExecutable && (element as ExecutableElement).isAsynchronous;
bool get isConst => false;
bool get isDeprecated {
// If element.metadata is empty, it might be because this is a property
// where the metadata belongs to the individual getter/setter
if (element.metadata.isEmpty && element is PropertyInducingElement) {
var pie = element as PropertyInducingElement;
// The getter or the setter might be null – so the stored value may be
// `true`, `false`, or `null`
var getterDeprecated = pie.getter?.metadata?.any((a) => a.isDeprecated);
var setterDeprecated = pie.setter?.metadata?.any((a) => a.isDeprecated);
var deprecatedValues =
[getterDeprecated, setterDeprecated].where((a) => a != null).toList();
// At least one of these should be non-null. Otherwise things are weird
assert(deprecatedValues.isNotEmpty);
// If there are both a setter and getter, only show the property as
// deprecated if both are deprecated.
return deprecatedValues.every((d) => d);
}
return element.metadata.any((a) => a.isDeprecated);
}
bool get isExecutable => element is ExecutableElement;
bool get isFinal => false;
bool get isLocalElement => element is LocalElement;
bool get isPropertyAccessor => element is PropertyAccessorElement;
bool get isPropertyInducer => element is PropertyInducingElement;
bool get isStatic {
if (isPropertyInducer) {
return (element as PropertyInducingElement).isStatic;
}
return false;
}
/// A human-friendly name for the kind of element this is.
String get kind;
String get linkedName {
if (_linkedName == null) {
_linkedName = _calculateLinkedName();
}
return _linkedName;
}
String get linkedParamsLines => linkedParams().trim();
String get linkedParamsNoMetadata => linkedParams(showMetadata: false);
ElementType get modelType => _modelType;
String get name => element.name;
@override
String get oneLineDoc => _documentation.asOneLiner;
ModelElement get overriddenElement => null;
Package get package =>
(this is Library) ? (this as Library).package : this.library.package;
List<Parameter> get parameters {
if (!canHaveParameters) {
throw new StateError("$element cannot have parameters");
}
if (_parameters != null) return _parameters;
List<ParameterElement> params;
if (element is ExecutableElement) {
// the as check silences the warning
params = (element as ExecutableElement).parameters;
}
if (element is FunctionTypeAliasElement) {
params = (element as FunctionTypeAliasElement).parameters;
}
_parameters =
params.map((p) => new Parameter(p, library)).toList(growable: false);
return _parameters;
}
String get _computeDocumentationComment => element.documentationComment;
Documentation get _documentation {
if (__documentation != null) return __documentation;
__documentation = new Documentation.forElement(this);
return __documentation;
}
bool canOverride() => element is ClassMemberElement;
int compareTo(dynamic other) {
if (other is ModelElement) {
return name.toLowerCase().compareTo(other.name.toLowerCase());
} else {
return 0;
}
}
String linkedParams(
{bool showMetadata: true, bool showNames: true, String separator: ', '}) {
String renderParam(Parameter p) {
StringBuffer buf = new StringBuffer();
buf.write('<span class="parameter" id="${p.htmlId}">');
if (showMetadata && p.hasAnnotations) {
buf.write('<ol class="annotation-list">');
p.annotations.forEach((String annotation) {
buf.write('<li>$annotation</li>');
});
buf.write('</ol> ');
}
if (p.modelType.isFunctionType) {
var returnTypeName;
if (p.modelType.element is Typedef) {
returnTypeName = p.modelType.linkedName;
} else {
returnTypeName = p.modelType.createLinkedReturnTypeName();
}
buf.write('<span class="type-annotation">${returnTypeName}</span>');
if (showNames) {
buf.write(' <span class="parameter-name">${p.name}</span>');
}
buf.write('(');
buf.write(p.modelType.element
.linkedParams(showNames: showNames, showMetadata: showMetadata));
buf.write(')');
} else if (p.modelType != null && p.modelType.element != null) {
var mt = p.modelType;
String typeName = "";
if (mt != null && !mt.isDynamic) {
typeName = mt.linkedName;
}
if (typeName.isNotEmpty) {
buf.write('<span class="type-annotation">$typeName</span> ');
}
if (showNames) {
buf.write('<span class="parameter-name">${p.name}</span>');
}
}
if (p.hasDefaultValue) {
if (p.isOptionalNamed) {
buf.write(': ');
} else {
buf.write(' = ');
}
buf.write('<span class="default-value">${p.defaultValue}</span>');
}
buf.write('</span>');
return buf.toString();
}
String renderParams(Iterable<Parameter> params,
{String open: '', String close: ''}) {
return '$open${params.map(renderParam).join(separator)}$close';
}
Iterable<Parameter> requiredParams =
parameters.where((Parameter p) => !p.isOptional);
Iterable<Parameter> positionalParams =
parameters.where((Parameter p) => p.isOptionalPositional);
Iterable<Parameter> namedParams =
parameters.where((Parameter p) => p.isOptionalNamed);
List<String> fragments = [];
if (requiredParams.isNotEmpty) {
fragments.add(renderParams(requiredParams));
}
if (positionalParams.isNotEmpty) {
fragments.add(renderParams(positionalParams, open: '[', close: ']'));
}
if (namedParams.isNotEmpty) {
fragments.add(renderParams(namedParams, open: '{', close: '}'));
}
return fragments.join(separator);
}
String toString() => '$runtimeType $name';
String _buildFullyQualifiedName([ModelElement e, String fqName]) {
e ??= this;
fqName ??= e.name;
if (e is! EnclosedElement) {
return fqName;
}
ModelElement parent = (e as EnclosedElement).enclosingElement;
return _buildFullyQualifiedName(parent, '${parent.name}.$fqName');
}
String _calculateLinkedName() {
if (name.startsWith('_')) {
return HTML_ESCAPE.convert(name);
}
if (!(this is Method || this is Field) && !package.isDocumented(element)) {
return HTML_ESCAPE.convert(name);
}
ModelElement c = (this is EnclosedElement)
? (this as EnclosedElement).enclosingElement
: null;
if (c != null) {
if (!package.isDocumented(c.element)) {
return HTML_ESCAPE.convert(name);
}
if (c.name.startsWith('_')) {
return '${c.name}.${HTML_ESCAPE.convert(name)}';
}
}
var classContent = '';
if (isDeprecated) {
classContent = 'class="deprecated" ';
}
return '<a ${classContent}href="${href}">$name</a>';
}
// process the {@example ...} in comments and inject the example
// code into the doc commment.
// {@example core/ts/bootstrap/bootstrap.ts region='bootstrap'}
String _injectExamples(String rawdocs) {
if (rawdocs.contains('@example')) {
RegExp exp = new RegExp(r"{@example .+}");
Iterable<Match> matches = exp.allMatches(rawdocs);
var dirPath = this.package.packageMeta.dir.path;
for (var match in matches) {
var strings = match.group(0).split(' ');
var path = strings[1];
if (path.contains(Platform.pathSeparator) &&
!path.startsWith(Platform.pathSeparator)) {
var file = new File(p.join(dirPath, 'examples', path));
if (file.existsSync()) {
// TODO(keertip):inject example
} else {
var filepath =
this.element.source.fullName.substring(dirPath.length + 1);
stdout.write(
'\nwarning: ${filepath}: file ${strings[1]} does not exist.');
}
}
}
}
return rawdocs;
}
}
class ModelFunction extends ModelElement
with SourceCodeMixin
implements EnclosedElement {
ModelFunction(FunctionElement element, Library library)
: super(element, library) {
_modelType = new ElementType(_func.type, this);
}
ModelElement get enclosingElement => library;
String get fileName => "$name.html";
@override
String get href => '${library.dirName}/$fileName';
bool get isStatic => _func.isStatic;
@override
String get kind => 'function';
String get linkedReturnType => modelType.createLinkedReturnTypeName();
FunctionElement get _func => (element as FunctionElement);
}
/// Something that has a name.
abstract class Nameable {
String get name;
}
class Operator extends Method {
static const Map<String, String> friendlyNames = const {
"[]": "get",
"[]=": "put",
"~": "bitwise_negate",
"==": "equals",
"-": "minus",
"+": "plus",
"*": "multiply",
"/": "divide",
"<": "less",
">": "greater",
">=": "greater_equal",
"<=": "less_equal",
"<<": "shift_left",
">>": "shift_right",
"^": "bitwise_exclusive_or",
"unary-": "unary_minus",
"|": "bitwise_or",
"&": "bitwise_and",
"~/": "truncate_divide",
"%": "modulo"
};
Operator(MethodElement element, Library library) : super(element, library);
Operator.inherited(
MethodElement element, Class enclosingClass, Library library)
: super.inherited(element, enclosingClass, library) {
_isInherited = true;
}
@override
String get fileName {
var actualName = super.name;
if (friendlyNames.containsKey(actualName)) {
return "operator_${friendlyNames[actualName]}.html";
} else {
return '$actualName.html';
}
}
@override
String get fullyQualifiedName =>
'${library.name}.${enclosingElement.name}.${super.name}';
bool get isOperator => true;
@override
String get name {
return 'operator ${super.name}';
}
String get typeName => 'operator';
}
class Package implements Nameable, Documentable {
final List<Library> _libraries = [];
final PackageMeta packageMeta;
final Map<String, Library> elementLibaryMap = {};
String _docsAsHtml;
Package(Iterable<LibraryElement> libraryElements, this.packageMeta) {
libraryElements.forEach((element) {
// add only if the element should be included in the public api
if (isPublic(element)) {
var lib = new Library(element, this);
Library._libraryMap.putIfAbsent(lib.name, () => lib);
elementLibaryMap.putIfAbsent('${lib.kind}.${lib.name}', () => lib);
_libraries.add(lib);
}
});
_libraries.forEach((library) {
library._allClasses.forEach(_addToImplementors);
});
_libraries.sort();
_implementors.values.forEach((l) => l.sort());
}
List<PackageCategory> get categories {
Map<String, PackageCategory> result = {};
for (Library library in _libraries) {
String name = '';
if (library.name.startsWith('dart:')) {
name = 'Dart Core';
} else {
name = library.packageName;
}
if (!result.containsKey(name)) result[name] = new PackageCategory(name);
result[name]._libraries.add(library);
}
return result.values.toList()..sort();
}
String get documentation {
return hasDocumentationFile ? documentationFile.contents : null;
}
String get documentationAsHtml {
if (_docsAsHtml != null) return _docsAsHtml;
_docsAsHtml = new Documentation(documentation).asHtml;
return _docsAsHtml;
}
FileContents get documentationFile => packageMeta.getReadmeContents();
// TODO: make this work
bool get hasDocumentation =>
documentationFile != null && documentationFile.contents.isNotEmpty;
// TODO: Clients should use [documentationFile] so they can act differently on
// plain text or markdown.
bool get hasDocumentationFile => documentationFile != null;
bool get hasMoreThanOneLineDocs => true;
// TODO: make this work
String get href => 'index.html';
/// Does this package represent the SDK?
bool get isSdk => packageMeta.isSdk;
List<Library> get libraries => _libraries;
String get name => packageMeta.name;
String get oneLineDoc => '';
String get version => packageMeta.version;
Library findLibraryFor(final Element element, {final ModelElement scopedTo}) {
if (element is LibraryElement) {
// will equality work here? or should we check names?
return _libraries.firstWhere((lib) => lib.element == element,
orElse: () => null);
}
Element el;
if (element is ClassMemberElement || element is PropertyAccessorElement) {
if (element.enclosingElement is! CompilationUnitElement) {
el = element.enclosingElement;
} else {
// get the library
el = element.enclosingElement.enclosingElement;
}
} else if (element is TopLevelVariableElement) {
final TopLevelVariableElement variableElement = element;
if (variableElement.getter != null) {
el = variableElement.getter;
} else if (variableElement.setter != null) {
el = variableElement.setter;
} else {
el = variableElement;
}
} else {
el = element;
}
return _libraries.firstWhere((lib) => lib.hasInExportedNamespace(el),
orElse: () => null);
}
bool isDocumented(Element element) => findLibraryFor(element) != null;
String toString() => isSdk ? 'SDK' : 'Package $name';
/// Will try to find the library that exports the element.
/// Checks if a library exports a name.
/// Can return null if not appropriate library can be found.
Library _getLibraryFor(Element e) {
// can be null if e is for dynamic
if (e.library == null) {
return null;
}
Library lib = elementLibaryMap['${e.kind}.${e.name}'];
if (lib != null) return lib;
lib =
libraries.firstWhere((l) => l.hasInExportedNamespace(e), orElse: () {});
if (lib != null) {
elementLibaryMap.putIfAbsent('${e.kind}.${e.name}', () => lib);
return lib;
}
return new Library(e.library, this);
}
}
class PackageCategory implements Comparable {
final String name;
final List<Library> _libraries = [];
PackageCategory(this.name);
List<Library> get libraries => _libraries;
int compareTo(PackageCategory other) => name.compareTo(other.name);
}
class Parameter extends ModelElement implements EnclosedElement {
Parameter(ParameterElement element, Library library)
: super(element, library) {
var t = _parameter.type;
_modelType = new ElementType(
t, new ModelElement.from(t.element, package._getLibraryFor(t.element)));
}
String get defaultValue {
if (!hasDefaultValue) return null;
return _parameter.defaultValueCode;
}
@override
ModelElement get enclosingElement =>
new ModelElement.from(_parameter.enclosingElement, library);
bool get hasDefaultValue {
return _parameter.defaultValueCode != null &&
_parameter.defaultValueCode.isNotEmpty;
}
@override
String get href {
var p = _parameter.enclosingElement;
if (p is FunctionElement) {
return '${library.dirName}/${p.name}.html';
} else {
// TODO: why is this logic here?
var name = Operator.friendlyNames.containsKey(p.name)
? Operator.friendlyNames[p.name]
: p.name;
return '${library.dirName}/${p.enclosingElement.name}/' +
'${name}.html#${htmlId}';
}
}
String get htmlId => '${_parameter.enclosingElement.name}-param-${name}';
bool get isOptional => _parameter.parameterKind.isOptional;
bool get isOptionalNamed => _parameter.parameterKind == ParameterKind.NAMED;
bool get isOptionalPositional =>
_parameter.parameterKind == ParameterKind.POSITIONAL;
@override
String get kind => 'parameter';
ParameterElement get _parameter => element as ParameterElement;
String toString() => element.name;
}
Map<String, Map<String, List<Map<String, dynamic>>>> __crossdartJson;
Map<String, Map<String, List<Map<String, dynamic>>>> get _crossdartJson {
if (__crossdartJson == null) {
if (config != null) {
var crossdartFile =
new File(p.join(config.inputDir.path, "crossdart.json"));
if (crossdartFile.existsSync()) {
__crossdartJson = JSON.decode(crossdartFile.readAsStringSync());
} else {
__crossdartJson = {};
}
} else {
__crossdartJson = {};
}
}
return __crossdartJson;
}
abstract class SourceCodeMixin {
String _sourceCodeCache;
String get crossdartHtmlTag {
if (config != null && config.addCrossdart && _crossdartUrl != null) {
return "<a class='crossdart' href='${_crossdartUrl}'>Link to Crossdart</a>";
} else {
return "";
}
}
Element get element;
bool get hasSourceCode => config.includeSource && sourceCode.isNotEmpty;
Library get library;
void clearSourceCodeCache() {
_sourceCodeCache = null;
}
String get sourceCode {
if (_sourceCodeCache == null) {
String contents = getFileContentsFor(element);
var node = element.computeNode();
if (node != null) {
// Find the start of the line, so that we can line up all the indents.
int i = node.offset;
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 = node.offset - (node.offset - i);
String source = contents.substring(start, node.end);
if (config != null && config.addCrossdart) {
source = crossdartifySource(_crossdartJson, source, element, start);
} else {
source = const HtmlEscape().convert(source);
}
source = stripIndentFromSource(source);
source = stripDartdocCommentsFromSource(source);
_sourceCodeCache = source.trim();
} else {
_sourceCodeCache = '';
}
}
return _sourceCodeCache;
}
String get _crossdartUrl {
if (_lineNumber != null && _crossdartPath != null) {
String url = "//www.crossdart.info/p/${_crossdartPath}.html";
return "${url}#line-${_lineNumber}";
} else {
return null;
}
}
int get _lineNumber {
var node = element.computeNode();
if (node is Declaration && (node as Declaration).element != null) {
var element = (node as Declaration).element;
var lineNumber = lineNumberCache.lineNumber(
element.source.fullName, element.nameOffset);
return lineNumber + 1;
} else {
return null;
}
}
String get _crossdartPath {
var node = element.computeNode();
if (node is Declaration && (node as Declaration).element != null) {
var source = ((node as Declaration).element.source as FileBasedSource);
var file = source.file.toString();
var uri = source.uri.toString();
var packageMeta = library.package.packageMeta;
if (uri.startsWith("package:")) {
var splittedUri =
uri.replaceAll(new RegExp(r"^package:"), "").split("/");
var packageName = splittedUri.first;
var packageVersion;
if (packageName == packageMeta.name) {
packageVersion = packageMeta.version;
} else {
var match = new RegExp(
".pub-cache/(hosted/pub.dartlang.org|git)/${packageName}-([^/]+)")
.firstMatch(file);
if (match != null) {
packageVersion = match[2];
}
}
if (packageVersion != null) {
return "${packageName}/${packageVersion}/${splittedUri.skip(1).join("/")}";
} else {
return null;
}
} else if (uri.startsWith("dart:")) {
var packageName = "sdk";
var packageVersion = config.sdkVersion;
return "${packageName}/${packageVersion}/lib/${uri.replaceAll(new RegExp(r"^dart:"), "")}";
} else {
return null;
}
} else {
return null;
}
}
}
/// Top-level variables. But also picks up getters and setters?
class TopLevelVariable extends ModelElement
with GetterSetterCombo
implements EnclosedElement {
TopLevelVariable(TopLevelVariableElement element, Library library)
: super(element, library) {
if (hasGetter) {
var t = _variable.getter.returnType;
_modelType = new ElementType(t,
new ModelElement.from(t.element, package._getLibraryFor(t.element)));
} else {
var s = _variable.setter.parameters.first.type;
_modelType = new ElementType(s,
new ModelElement.from(s.element, package._getLibraryFor(s.element)));
}
}
String get constantValue {
var v = _variable.computeNode().toSource();
if (v == null) return '';
var string = v.substring(v.indexOf('=') + 1, v.length).trim();
return string.replaceAll(modelType.name, modelType.linkedName);
}
@override
ModelElement get enclosingElement => library;
bool get hasGetter => _variable.getter != null;
bool get hasSetter => _variable.setter != null;
@override
String get href => '${library.dirName}/$_fileName';
bool get isConst => _variable.isConst;
bool get isFinal => _variable.isFinal;
@override
String get kind => 'top-level property';
String get linkedReturnType => modelType.linkedName;
bool get readOnly => hasGetter && !hasSetter;
bool get readWrite => hasGetter && hasSetter;
bool get writeOnly => hasSetter && !hasGetter;
@override
String get _computeDocumentationComment {
String docs = getterSetterDocumentationComment;
if (docs.isEmpty) return _variable.documentationComment;
return docs;
}
String get _fileName => isConst ? '$name-constant.html' : '$name.html';
PropertyAccessorElement get _getter => _variable.getter;
PropertyAccessorElement get _setter => _variable.setter;
TopLevelVariableElement get _variable => (element as TopLevelVariableElement);
}
class Typedef extends ModelElement implements EnclosedElement {
Typedef(FunctionTypeAliasElement element, Library library)
: super(element, library) {
if (element.type != null) {
_modelType = new ElementType(element.type, this);
}
}
@override
ModelElement get enclosingElement => library;
String get fileName => '$name.html';
@override
String get href => '${library.dirName}/$fileName';
@override
String get kind => 'typedef';
String get linkedReturnType => modelType != null
? modelType.createLinkedReturnTypeName()
: _typedef.returnType.name;
String get nameWithGenerics {
if (!modelType.isParameterizedType) return name;
return '$name&lt;${_typeParameters.map((t) => t.name).join(', ')}&gt;';
}
FunctionTypeAliasElement get _typedef =>
(element as FunctionTypeAliasElement);
List<TypeParameter> get _typeParameters => _typedef.typeParameters.map((f) {
return new TypeParameter(f, library);
}).toList();
}
class TypeParameter extends ModelElement {
TypeParameter(TypeParameterElement element, Library library)
: super(element, library) {
_modelType = new ElementType(_typeParameter.type, this);
}
@override
String get href =>
'${library.dirName}/${_typeParameter.enclosingElement.name}/$name';
@override
String get kind => 'type parameter';
String get name {
var bound = _typeParameter.bound;
return bound != null
? '${_typeParameter.name} extends ${bound.name}'
: _typeParameter.name;
}
TypeParameterElement get _typeParameter => element as TypeParameterElement;
String toString() => element.name;
}