blob: 2e59a61db755879aaaa682095579782b749df0ba [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.
library docgen.models;
import 'dart:io';
import 'package:markdown/markdown.dart' as markdown;
import '../../../../sdk/lib/_internal/compiler/implementation/mirrors/source_mirrors.dart';
import '../../../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors_util.dart'
as dart2js_util;
import '../../../../sdk/lib/_internal/compiler/implementation/mirrors/dart2js_mirrors.dart'
as dart2js_mirrors;
import 'library_helpers.dart';
import 'mdn.dart';
import 'model_helpers.dart';
import 'package_helpers.dart';
/// Docgen representation of an item to be documented, that wraps around a
/// dart2js mirror.
abstract class MirrorBased<TMirror extends DeclarationMirror> {
/// The original dart2js mirror around which this object wraps.
TMirror get mirror;
/// Return an informative [Object.toString] for debugging.
String toString() => "${super.toString()} - $mirror";
}
/// A Docgen wrapper around the dart2js mirror for a generic type.
class Generic extends MirrorBased<TypeVariableMirror> {
final TypeVariableMirror mirror;
Generic(this.mirror);
Map toMap() => {
'name': dart2js_util.nameOf(mirror),
'type': dart2js_util.qualifiedNameOf(mirror.upperBound)
};
}
/// For types that we do not explicitly create or have not yet created in our
/// entity map (like core types).
class DummyMirror implements Indexable {
final DeclarationMirror mirror;
/// The library that contains this element, if any. Used as a hint to help
/// determine which object we're referring to when looking up this mirror in
/// our map.
final Indexable owner;
DummyMirror(this.mirror, [this.owner]);
String get docName {
if (mirror == null) return '';
if (mirror is LibraryMirror) {
return dart2js_util.qualifiedNameOf(mirror).replaceAll('.','-');
}
var mirrorOwner = mirror.owner;
if (mirrorOwner == null) return dart2js_util.qualifiedNameOf(mirror);
var simpleName = dart2js_util.nameOf(mirror);
if (mirror is MethodMirror && (mirror as MethodMirror).isConstructor) {
// We name constructors specially -- repeating the class name and a
// "-" to separate the constructor from its name (if any).
simpleName = '${dart2js_util.nameOf(mirrorOwner)}-$simpleName';
}
return getDocgenObject(mirrorOwner, owner).docName + '.' +
simpleName;
}
bool get isPrivate => mirror == null? false : mirror.isPrivate;
String get packageName {
var libMirror = _getOwningLibraryFromMirror(mirror);
if (libMirror != null) {
return getPackageName(libMirror);
}
return '';
}
String get packagePrefix => packageName == null || packageName.isEmpty ?
'' : '$packageName/';
LibraryMirror _getOwningLibraryFromMirror(DeclarationMirror mirror) {
if (mirror is LibraryMirror) return mirror;
if (mirror == null) return null;
return _getOwningLibraryFromMirror(mirror.owner);
}
noSuchMethod(Invocation invocation) {
throw new UnimplementedError(invocation.memberName.toString());
}
}
/// An item that is categorized in our mirrorToDocgen map, as a distinct,
/// searchable element.
///
/// These are items that refer to concrete entities (a Class, for example,
/// but not a Type, which is a "pointer" to a class) that we wish to be
/// globally resolvable. This includes things such as class methods and
/// variables, but parameters for methods are not "Indexable" as we do not want
/// the user to be able to search for a method based on its parameter names!
/// The set of indexable items also includes Typedefs, since the user can refer
/// to them as concrete entities in a particular scope.
abstract class Indexable<TMirror extends DeclarationMirror>
extends MirrorBased<TMirror> {
Library get owningLibrary => owner.owningLibrary;
String get qualifiedName => fileName;
final TMirror mirror;
final bool isPrivate;
/// The comment text pre-resolution. We keep this around because inherited
/// methods need to resolve links differently from the superclass.
String _unresolvedComment = '';
Indexable(TMirror mirror)
: this.mirror = mirror,
this.isPrivate = isHidden(mirror) {
var map = mirrorToDocgen[dart2js_util.qualifiedNameOf(this.mirror)];
if (map == null) map = new Map<String, Set<Indexable>>();
var set = map[owner.docName];
if (set == null) set = new Set<Indexable>();
set.add(this);
map[owner.docName] = set;
mirrorToDocgen[dart2js_util.qualifiedNameOf(this.mirror)] = map;
}
/// Returns this object's qualified name, but following the conventions
/// we're using in Dartdoc, which is that library names with dots in them
/// have them replaced with hyphens.
String get docName;
/// Converts all [foo] references in comments to <a>libraryName.foo</a>.
markdown.Node fixReference(String name) {
// Attempt the look up the whole name up in the scope.
String elementName = findElementInScope(name);
if (elementName != null) {
return new markdown.Element.text('a', elementName);
}
return fixComplexReference(name);
}
/// Look for the specified name starting with the current member, and
/// progressively working outward to the current library scope.
String findElementInScope(String name) =>
findElementInScopeWithPrefix(name, packagePrefix);
/// The reference to this element based on where it is printed as a
/// documentation file and also the unique URL to refer to this item.
///
/// The qualified name (for URL purposes) and the file name are the same,
/// of the form packageName/ClassName or packageName/ClassName.methodName.
/// This defines both the URL and the directory structure.
String get fileName => packagePrefix + ownerPrefix + name;
/// The full docName of the owner element, appended with a '.' for this
/// object's name to be appended.
String get ownerPrefix => owner.docName != '' ? owner.docName + '.' : '';
/// The prefix String to refer to the package that this item is in, for URLs
/// and comment resolution.
///
/// The prefix can be prepended to a qualified name to get a fully unique
/// name among all packages.
String get packagePrefix => '';
/// Documentation comment with converted markdown and all links resolved.
String _comment;
/// Accessor to documentation comment with markdown converted to html and all
/// links resolved.
String get comment {
if (_comment != null) return _comment;
_comment = _commentToHtml();
if (_comment.isEmpty) {
_comment = _mdnComment();
}
return _comment;
}
void set comment(x) {
_comment = x;
}
/// The simple name to refer to this item.
String get name => dart2js_util.nameOf(mirror);
/// Accessor to the parent item that owns this item.
///
/// "Owning" is defined as the object one scope-level above which this item
/// is defined. Ex: The owner for a top level class, would be its enclosing
/// library. The owner of a local variable in a method would be the enclosing
/// method.
Indexable get owner => new DummyMirror(mirror.owner);
/// Generates MDN comments from database.json.
String _mdnComment();
/// The type of this member to be used in index.txt.
String get typeName => '';
/// Creates a [Map] with this [Indexable]'s name and a preview comment.
Map get previewMap {
var finalMap = { 'name' : name, 'qualifiedName' : qualifiedName };
var preview = _preview;
if(preview != null) finalMap['preview'] = preview;
return finalMap;
}
String get _preview {
if (comment != '') {
var index = comment.indexOf('</p>');
return index > 0 ?
'${comment.substring(0, index)}</p>' :
'<p><i>Comment preview not available</i></p>';
}
return null;
}
/// Accessor to obtain the raw comment text for a given item, _without_ any
/// of the links resolved.
String get _commentText {
String commentText;
mirror.metadata.forEach((metadata) {
if (metadata is CommentInstanceMirror) {
CommentInstanceMirror comment = metadata;
if (comment.isDocComment) {
if (commentText == null) {
commentText = comment.trimmedText;
} else {
commentText = '$commentText\n${comment.trimmedText}';
}
}
}
});
return commentText;
}
/// Returns any documentation comments associated with a mirror with
/// simple markdown converted to html.
///
/// By default we resolve any comment references within our own scope.
/// However, if a method is inherited, we want the inherited comments, but
/// links to the subclasses's version of the methods.
String _commentToHtml([Indexable resolvingScope]) {
if (resolvingScope == null) resolvingScope = this;
var commentText = _commentText;
_unresolvedComment = commentText;
var linkResolver = (name) => resolvingScope.fixReference(name);
commentText = commentText == null ? '' :
markdown.markdownToHtml(commentText.trim(), linkResolver: linkResolver,
inlineSyntaxes: MARKDOWN_SYNTAXES);
return commentText;
}
/// Return a map representation of this type.
Map toMap();
/// Expand the method map [mapToExpand] into a more detailed map that
/// separates out setters, getters, constructors, operators, and methods.
Map _expandMethodMap(Map<String, Method> mapToExpand) => {
'setters': recurseMap(filterMap(mapToExpand,
(key, val) => val.mirror.isSetter)),
'getters': recurseMap(filterMap(mapToExpand,
(key, val) => val.mirror.isGetter)),
'constructors': recurseMap(filterMap(mapToExpand,
(key, val) => val.mirror.isConstructor)),
'operators': recurseMap(filterMap(mapToExpand,
(key, val) => val.mirror.isOperator)),
'methods': recurseMap(filterMap(mapToExpand,
(key, val) => val.mirror.isRegularMethod && !val.mirror.isOperator))
};
/// Accessor to determine if this item and all of its owners are visible.
bool get isVisible => isFullChainVisible(this);
/// Returns true if [mirror] is the correct type of mirror that this Docgen
/// object wraps. (Workaround for the fact that Types are not first class.)
bool isValidMirror(DeclarationMirror mirror);
}
/// A class containing contents of a Dart library.
class Library extends Indexable {
final Map<String, Class> classes = {};
final Map<String, Typedef> typedefs = {};
final Map<String, Class> errors = {};
/// Top-level variables in the library.
Map<String, Variable> variables;
/// Top-level functions in the library.
Map<String, Method> functions;
String packageName = '';
bool _hasBeenCheckedForPackage = false;
String packageIntro;
Library get owningLibrary => this;
/// Returns the [Library] for the given [mirror] if it has already been
/// created, else creates it.
factory Library(LibraryMirror mirror) {
var library = getDocgenObject(mirror);
if (library is DummyMirror) {
library = new Library._(mirror);
}
return library;
}
Library._(LibraryMirror libraryMirror) : super(libraryMirror) {
var exported = calcExportedItems(libraryMirror);
var exportedClasses = addAll(exported['classes'],
dart2js_util.typesOf(libraryMirror.declarations));
updateLibraryPackage(mirror);
exportedClasses.forEach((String mirrorName, TypeMirror mirror) {
if (mirror is TypedefMirror) {
// This is actually a Dart2jsTypedefMirror, and it does define value,
// but we don't have visibility to that type.
if (includePrivateMembers || !mirror.isPrivate) {
typedefs[dart2js_util.nameOf(mirror)] = new Typedef(mirror, this);
}
} else if (mirror is ClassMirror) {
var clazz = new Class(mirror, this);
if (clazz.isError()) {
errors[dart2js_util.nameOf(mirror)] = clazz;
} else {
classes[dart2js_util.nameOf(mirror)] = clazz;
}
} else {
throw new ArgumentError(
'${dart2js_util.nameOf(mirror)} - no class type match. ');
}
});
this.functions = createMethods(addAll(exported['methods'],
libraryMirror.declarations.values.where(
(mirror) => mirror is MethodMirror)).values, this);
this.variables = createVariables(addAll(exported['variables'],
dart2js_util.variablesOf(libraryMirror.declarations)).values, this);
}
/// Look for the specified name starting with the current member, and
/// progressively working outward to the current library scope.
String findElementInScope(String name) {
var lookupFunc = determineLookupFunc(name);
var libraryScope = lookupFunc(mirror, name);
if (libraryScope != null) {
var result = getDocgenObject(libraryScope, this);
if (result is DummyMirror) return packagePrefix + result.docName;
return result.packagePrefix + result.docName;
}
return super.findElementInScope(name);
}
String _mdnComment() => '';
/// For a library's [mirror], determine the name of the package (if any) we
/// believe it came from (because of its file URI).
///
/// If no package could be determined, we return an empty string.
void updateLibraryPackage(LibraryMirror mirror) {
if (mirror == null) return;
if (_hasBeenCheckedForPackage) return;
_hasBeenCheckedForPackage = true;
if (mirror.uri.scheme != 'file') return;
packageName = getPackageName(mirror);
// Associate the package readme with all the libraries. This is a bit
// wasteful, but easier than trying to figure out which partial match
// is best.
packageIntro = _packageIntro(getPackageDirectory(mirror));
}
String _packageIntro(packageDir) {
if (packageDir == null) return null;
var dir = new Directory(packageDir);
var files = dir.listSync();
var readmes = files.where((FileSystemEntity each) => (each is File &&
each.path.substring(packageDir.length + 1, each.path.length)
.startsWith('README'))).toList();
if (readmes.isEmpty) return '';
// If there are multiples, pick the shortest name.
readmes.sort((a, b) => a.path.length.compareTo(b.path.length));
var readme = readmes.first;
var linkResolver = (name) => globalFixReference(name);
var contents = markdown.markdownToHtml(readme
.readAsStringSync(), linkResolver: linkResolver,
inlineSyntaxes: MARKDOWN_SYNTAXES);
return contents;
}
String get packagePrefix => packageName == null || packageName.isEmpty ?
'' : '$packageName/';
Map get previewMap {
var basic = super.previewMap;
basic['packageName'] = packageName;
if (packageIntro != null) {
basic['packageIntro'] = packageIntro;
}
return basic;
}
String get name => docName;
String get docName {
return dart2js_util.qualifiedNameOf(mirror).replaceAll('.','-');
}
/// Checks if the given name is a key for any of the Class Maps.
bool containsKey(String name) =>
classes.containsKey(name) || errors.containsKey(name);
/// Generates a map describing the [Library] object.
Map toMap() => {
'name': name,
'qualifiedName': qualifiedName,
'comment': comment,
'variables': recurseMap(variables),
'functions': _expandMethodMap(functions),
'classes': {
'class': classes.values.where((c) => c.isVisible)
.map((e) => e.previewMap).toList(),
'typedef': recurseMap(typedefs),
'error': errors.values.where((e) => e.isVisible)
.map((e) => e.previewMap).toList()
},
'packageName': packageName,
'packageIntro' : packageIntro
};
String get typeName => 'library';
bool isValidMirror(DeclarationMirror mirror) => mirror is LibraryMirror;
}
abstract class OwnedIndexable<TMirror extends DeclarationMirror>
extends Indexable<TMirror> {
/// List of the meta annotations on this item.
final List<Annotation> annotations;
/// The object one scope-level above which this item is defined.
///
/// Ex: The owner for a top level class, would be its enclosing library.
/// The owner of a local variable in a method would be the enclosing method.
final Indexable owner;
/// Returns this object's qualified name, but following the conventions
/// we're using in Dartdoc, which is that library names with dots in them
/// have them replaced with hyphens.
String get docName => owner.docName + '.' + dart2js_util.nameOf(mirror);
OwnedIndexable(DeclarationMirror mirror, Indexable owner)
: annotations = createAnnotations(mirror, owner.owningLibrary),
this.owner = owner,
super(mirror);
/// Generates MDN comments from database.json.
String _mdnComment() {
var domAnnotation = this.annotations.firstWhere(
(e) => e.mirror.qualifiedName == #metadata.DomName,
orElse: () => null);
if (domAnnotation == null) return '';
var domName = domAnnotation.parameters.single;
return mdnComment(rootDirectory, logger, domName);
}
String get packagePrefix => owner.packagePrefix;
}
/// A class containing contents of a Dart class.
class Class
extends OwnedIndexable<dart2js_mirrors.Dart2JsInterfaceTypeMirror>
implements Comparable<Class> {
/// List of the names of interfaces that this class implements.
List<Class> interfaces = [];
/// Names of classes that extends or implements this class.
Set<Class> subclasses = new Set<Class>();
/// Top-level variables in the class.
Map<String, Variable> variables;
/// Inherited variables in the class.
final Map<String, Variable> inheritedVariables = {};
/// Methods in the class.
Map<String, Method> methods;
final Map<String, Method> inheritedMethods = new Map<String, Method>();
/// Generic infomation about the class.
final Map<String, Generic> generics;
Class superclass;
bool get isAbstract => mirror.isAbstract;
/// Make sure that we don't check for inherited comments more than once.
bool _commentsEnsured = false;
/// Returns the [Class] for the given [mirror] if it has already been created,
/// else creates it.
factory Class(ClassMirror mirror, Library owner) {
var clazz = getDocgenObject(mirror, owner);
if (clazz is DummyMirror) {
clazz = new Class._(mirror, owner);
}
return clazz;
}
/// Called when we are constructing a superclass or interface class, but it
/// is not known if it belongs to the same owner as the original class. In
/// this case, we create an object whose owner is what the original mirror
/// says it is.
factory Class._possiblyDifferentOwner(ClassMirror mirror,
Library originalOwner) {
if (mirror.owner is LibraryMirror) {
var realOwner = getDocgenObject(mirror.owner);
if (realOwner is Library) {
return new Class(mirror, realOwner);
} else {
return new Class(mirror, originalOwner);
}
} else {
return new Class(mirror, originalOwner);
}
}
Class._(ClassSourceMirror classMirror, Indexable owner)
: generics = createGenerics(classMirror),
super(classMirror, owner) {
// The reason we do this madness is the superclass and interface owners may
// not be this class's owner!! Example: BaseClient in http pkg.
var superinterfaces = classMirror.superinterfaces.map(
(interface) => new Class._possiblyDifferentOwner(interface, owner));
this.superclass = classMirror.superclass == null? null :
new Class._possiblyDifferentOwner(classMirror.superclass, owner);
interfaces = superinterfaces.toList();
variables = createVariables(
dart2js_util.variablesOf(classMirror.declarations), this);
methods = createMethods(classMirror.declarations.values.where(
(mirror) => mirror is MethodMirror), this);
// Tell superclass that you are a subclass, unless you are not
// visible or an intermediary mixin class.
if (!classMirror.isNameSynthetic && isVisible && superclass != null) {
superclass.addSubclass(this);
}
if (this.superclass != null) addInherited(superclass);
interfaces.forEach((interface) => addInherited(interface));
}
String _lookupInClassAndSuperclasses(String name) {
var lookupFunc = determineLookupFunc(name);
var classScope = this;
while (classScope != null) {
var classFunc = lookupFunc(classScope.mirror, name);
if (classFunc != null) {
return packagePrefix + getDocgenObject(classFunc, owner).docName;
}
classScope = classScope.superclass;
}
return null;
}
/// Look for the specified name starting with the current member, and
/// progressively working outward to the current library scope.
String findElementInScope(String name) {
var lookupFunc = determineLookupFunc(name);
var result = _lookupInClassAndSuperclasses(name);
if (result != null) {
return result;
}
result = owner.findElementInScope(name);
return result == null ? super.findElementInScope(name) : result;
}
String get typeName => 'class';
/// Add all inherited variables and methods from the provided superclass.
/// If [_includePrivate] is true, it also adds the variables and methods from
/// the superclass.
void addInherited(Class superclass) {
inheritedVariables.addAll(superclass.inheritedVariables);
inheritedVariables.addAll(_allButStatics(superclass.variables));
addInheritedMethod(superclass, this);
}
/** [newParent] refers to the actual class is currently using these methods.
* which may be different because with the mirror system, we only point to the
* original canonical superclasse's method.
*/
void addInheritedMethod(Class parent, Class newParent) {
parent.inheritedMethods.forEach((name, method) {
if(!method.mirror.isConstructor){
inheritedMethods[name] = new Method(method.mirror, newParent, method);
}}
);
_allButStatics(parent.methods).forEach((name, method) {
if (!method.mirror.isConstructor) {
inheritedMethods[name] = new Method(method.mirror, newParent, method);
}}
);
}
/// Remove statics from the map of inherited items before adding them.
Map _allButStatics(Map items) {
var result = {};
items.forEach((name, item) {
if (!item.isStatic) {
result[name] = item;
}
});
return result;
}
/// Add the subclass to the class.
///
/// If [this] is private (or an intermediary mixin class), it will add the
/// subclass to the list of subclasses in the superclasses.
void addSubclass(Class subclass) {
if (docName == 'dart-core.Object') return;
if (!includePrivateMembers && isPrivate || mirror.isNameSynthetic) {
if (superclass != null) superclass.addSubclass(subclass);
interfaces.forEach((interface) {
interface.addSubclass(subclass);
});
} else {
subclasses.add(subclass);
}
}
/// Check if this [Class] is an error or exception.
bool isError() {
if (qualifiedName == 'dart-core.Error' ||
qualifiedName == 'dart-core.Exception')
return true;
for (var interface in interfaces) {
if (interface.isError()) return true;
}
if (superclass == null) return false;
return superclass.isError();
}
/// Makes sure that all methods with inherited equivalents have comments.
void ensureComments() {
if (_commentsEnsured) return;
_commentsEnsured = true;
if (superclass != null) superclass.ensureComments();
inheritedMethods.forEach((qualifiedName, inheritedMethod) {
var method = methods[qualifiedName];
if (method != null) {
// if we have overwritten this method in this class, we still provide
// the opportunity to inherit the comments.
method.ensureCommentFor(inheritedMethod);
}
});
// we need to populate the comments for all methods. so that the subclasses
// can get for their inherited versions the comments.
methods.forEach((qualifiedName, method) {
if (!method.mirror.isConstructor) method.ensureCommentFor(method);
});
}
/// If a class extends a private superclass, find the closest public
/// superclass of the private superclass.
String validSuperclass() {
if (superclass == null) return 'dart-core.Object';
if (superclass.isVisible) return superclass.qualifiedName;
return superclass.validSuperclass();
}
/// Generates a map describing the [Class] object.
Map toMap() => {
'name': name,
'qualifiedName': qualifiedName,
'comment': comment,
'isAbstract' : isAbstract,
'superclass': validSuperclass(),
'implements': interfaces.where((i) => i.isVisible)
.map((e) => e.qualifiedName).toList(),
'subclass': (subclasses.toList()..sort())
.map((x) => x.qualifiedName).toList(),
'variables': recurseMap(variables),
'inheritedVariables': recurseMap(inheritedVariables),
'methods': _expandMethodMap(methods),
'inheritedMethods': _expandMethodMap(inheritedMethods),
'annotations': annotations.map((a) => a.toMap()).toList(),
'generics': recurseMap(generics)
};
int compareTo(Class other) => name.compareTo(other.name);
bool isValidMirror(DeclarationMirror mirror) => mirror is ClassMirror;
}
class Typedef extends OwnedIndexable {
final String returnType;
final Map<String, Parameter> parameters;
/// Generic information about the typedef.
final Map<String, Generic> generics;
/// Returns the [Library] for the given [mirror] if it has already been
/// created, else creates it.
factory Typedef(TypedefMirror mirror, Library owningLibrary) {
var aTypedef = getDocgenObject(mirror, owningLibrary);
if (aTypedef is DummyMirror) {
aTypedef = new Typedef._(mirror, owningLibrary);
}
return aTypedef;
}
Typedef._(TypedefMirror mirror, Library owningLibrary)
: returnType = getDocgenObject(mirror.referent.returnType).docName,
generics = createGenerics(mirror),
parameters = createParameters(mirror.referent.parameters,
owningLibrary),
super(mirror, owningLibrary);
Map toMap() {
var map = {
'name': name,
'qualifiedName': qualifiedName,
'comment': comment,
'return': returnType,
'parameters': recurseMap(parameters),
'annotations': annotations.map((a) => a.toMap()).toList(),
'generics': recurseMap(generics)
};
// Typedef is displayed on the library page as a class, so a preview is
// added manually
var preview = _preview;
if(preview != null) map['preview'] = preview;
return map;
}
markdown.Node fixReference(String name) => null;
String get typeName => 'typedef';
bool isValidMirror(DeclarationMirror mirror) => mirror is TypedefMirror;
}
/// A class containing properties of a Dart variable.
class Variable extends OwnedIndexable {
bool isFinal;
bool isStatic;
bool isConst;
Type type;
String _variableName;
factory Variable(String variableName, VariableMirror mirror,
Indexable owner) {
var variable = getDocgenObject(mirror);
if (variable is DummyMirror) {
return new Variable._(variableName, mirror, owner);
}
return variable;
}
Variable._(this._variableName, VariableMirror mirror, Indexable owner) :
super(mirror, owner) {
isFinal = mirror.isFinal;
isStatic = mirror.isStatic;
isConst = mirror.isConst;
type = new Type(mirror.type, owner.owningLibrary);
}
String get name => _variableName;
/// Generates a map describing the [Variable] object.
Map toMap() => {
'name': name,
'qualifiedName': qualifiedName,
'comment': comment,
'final': isFinal,
'static': isStatic,
'constant': isConst,
'type': new List.filled(1, type.toMap()),
'annotations': annotations.map((a) => a.toMap()).toList()
};
String get typeName => 'property';
get comment {
if (_comment != null) return _comment;
if (owner is Class) {
(owner as Class).ensureComments();
}
return super.comment;
}
String findElementInScope(String name) {
var lookupFunc = determineLookupFunc(name);
var result = lookupFunc(mirror, name);
if (result != null) {
result = getDocgenObject(result);
if (result is DummyMirror) return packagePrefix + result.docName;
return result.packagePrefix + result.docName;
}
if (owner != null) {
var result = owner.findElementInScope(name);
if (result != null) {
return result;
}
}
return super.findElementInScope(name);
}
bool isValidMirror(DeclarationMirror mirror) => mirror is VariableMirror;
}
/// A class containing properties of a Dart method.
class Method extends OwnedIndexable {
/// Parameters for this method.
final Map<String, Parameter> parameters;
final bool isStatic;
final bool isAbstract;
final bool isConst;
final Type returnType;
Method methodInheritedFrom;
/// Qualified name to state where the comment is inherited from.
String commentInheritedFrom = "";
factory Method(MethodMirror mirror, Indexable owner,
[Method methodInheritedFrom]) {
var method = getDocgenObject(mirror, owner);
if (method is DummyMirror) {
method = new Method._(mirror, owner, methodInheritedFrom);
}
return method;
}
Method._(MethodMirror mirror, Indexable owner, this.methodInheritedFrom)
: returnType = new Type(mirror.returnType, owner.owningLibrary),
isStatic = mirror.isStatic,
isAbstract = mirror.isAbstract,
isConst = mirror.isConstConstructor,
parameters = createParameters(mirror.parameters, owner),
super(mirror, owner);
Method get originallyInheritedFrom => methodInheritedFrom == null ?
this : methodInheritedFrom.originallyInheritedFrom;
/// Look for the specified name starting with the current member, and
/// progressively working outward to the current library scope.
String findElementInScope(String name) {
var lookupFunc = determineLookupFunc(name);
var memberScope = lookupFunc(this.mirror, name);
if (memberScope != null) {
// do we check for a dummy mirror returned here and look up with an owner
// higher ooooor in getDocgenObject do we include more things in our
// lookup
var result = getDocgenObject(memberScope, owner);
if (result is DummyMirror && owner.owner != null
&& owner.owner is! DummyMirror) {
var aresult = getDocgenObject(memberScope, owner.owner);
if (aresult is! DummyMirror) result = aresult;
}
if (result is DummyMirror) return packagePrefix + result.docName;
return result.packagePrefix + result.docName;
}
if (owner != null) {
var result = owner.findElementInScope(name);
if (result != null) return result;
}
return super.findElementInScope(name);
}
String get docName {
if ((mirror as MethodMirror).isConstructor) {
// We name constructors specially -- including the class name again and a
// "-" to separate the constructor from its name (if any).
return '${owner.docName}.${dart2js_util.nameOf(mirror.owner)}-'
'${dart2js_util.nameOf(mirror)}';
}
return super.docName;
}
String get fileName => packagePrefix + docName;
/// Makes sure that the method with an inherited equivalent have comments.
void ensureCommentFor(Method inheritedMethod) {
if (comment.isNotEmpty) return;
comment = inheritedMethod._commentToHtml(this);
_unresolvedComment = inheritedMethod._unresolvedComment;
commentInheritedFrom = inheritedMethod.commentInheritedFrom == '' ?
new DummyMirror(inheritedMethod.mirror).docName :
inheritedMethod.commentInheritedFrom;
}
/// Generates a map describing the [Method] object.
Map toMap() => {
'name': name,
'qualifiedName': qualifiedName,
'comment': comment,
'commentFrom': (methodInheritedFrom != null &&
commentInheritedFrom == methodInheritedFrom.docName ? ''
: commentInheritedFrom),
'inheritedFrom': (methodInheritedFrom == null? '' :
originallyInheritedFrom.docName),
'static': isStatic,
'abstract': isAbstract,
'constant': isConst,
'return': [returnType.toMap()],
'parameters': recurseMap(parameters),
'annotations': annotations.map((a) => a.toMap()).toList()
};
String get typeName {
MethodMirror theMirror = mirror;
if (theMirror.isConstructor) return 'constructor';
if (theMirror.isGetter) return 'getter';
if (theMirror.isSetter) return'setter';
if (theMirror.isOperator) return 'operator';
return 'method';
}
get comment {
if (_comment != null) return _comment;
if (owner is Class) {
(owner as Class).ensureComments();
}
var result = super.comment;
if (result == '' && methodInheritedFrom != null) {
// This should be NOT from the MIRROR, but from the COMMENT.
methodInheritedFrom.comment; // Ensure comment field has been populated.
_unresolvedComment = methodInheritedFrom._unresolvedComment;
var linkResolver = (name) => fixReference(name);
comment = _unresolvedComment == null ? '' :
markdown.markdownToHtml(_unresolvedComment.trim(),
linkResolver: linkResolver, inlineSyntaxes: MARKDOWN_SYNTAXES);
commentInheritedFrom = comment != '' ?
methodInheritedFrom.commentInheritedFrom : '';
result = comment;
}
return result;
}
bool isValidMirror(DeclarationMirror mirror) => mirror is MethodMirror;
}
/// Docgen wrapper around the dart2js mirror for a Dart
/// method/function parameter.
class Parameter extends MirrorBased {
final ParameterMirror mirror;
final String name;
final bool isOptional;
final bool isNamed;
final bool hasDefaultValue;
final Type type;
final String defaultValue;
/// List of the meta annotations on the parameter.
final List<Annotation> annotations;
Parameter(ParameterMirror mirror, Library owningLibrary)
: this.mirror = mirror,
name = dart2js_util.nameOf(mirror),
isOptional = mirror.isOptional,
isNamed = mirror.isNamed,
hasDefaultValue = mirror.hasDefaultValue,
defaultValue = '${mirror.defaultValue}',
type = new Type(mirror.type, owningLibrary),
annotations = createAnnotations(mirror, owningLibrary);
/// Generates a map describing the [Parameter] object.
Map toMap() => {
'name': name,
'optional': isOptional,
'named': isNamed,
'default': hasDefaultValue,
'type': new List.filled(1, type.toMap()),
'value': defaultValue,
'annotations': annotations.map((a) => a.toMap()).toList()
};
}
/// Docgen wrapper around the mirror for a return type, and/or its generic
/// type parameters.
///
/// Return types are of a form [outer]<[inner]>.
/// If there is no [inner] part, [inner] will be an empty list.
///
/// For example:
/// int size()
/// "return" :
/// - "outer" : "dart-core.int"
/// "inner" :
///
/// List<String> toList()
/// "return" :
/// - "outer" : "dart-core.List"
/// "inner" :
/// - "outer" : "dart-core.String"
/// "inner" :
///
/// Map<String, List<int>>
/// "return" :
/// - "outer" : "dart-core.Map"
/// "inner" :
/// - "outer" : "dart-core.String"
/// "inner" :
/// - "outer" : "dart-core.List"
/// "inner" :
/// - "outer" : "dart-core.int"
/// "inner" :
class Type extends MirrorBased {
final TypeMirror mirror;
final Library owningLibrary;
Type(this.mirror, this.owningLibrary);
Map toMap() {
var result = getDocgenObject(mirror, owningLibrary);
return {
// We may encounter types whose corresponding library has not been
// processed yet, so look up with the owningLibrary at the last moment.
'outer': result.packagePrefix + result.docName,
'inner': _createTypeGenerics(mirror).map((e) => e.toMap()).toList(),
};
}
/// Returns a list of [Type] objects constructed from TypeMirrors.
List<Type> _createTypeGenerics(TypeMirror mirror) {
if (mirror is! ClassMirror) return [];
return mirror.typeArguments.map((e) => new Type(e, owningLibrary)).toList();
}
}
/// Holds the name of the annotation, and its parameters.
class Annotation extends MirrorBased {
/// The class of this annotation.
final ClassMirror mirror;
final Library owningLibrary;
List<String> parameters;
Annotation(InstanceMirror originalMirror, this.owningLibrary)
: mirror = originalMirror.type {
parameters = dart2js_util.variablesOf(originalMirror.type.declarations)
.where((e) => e.isFinal)
.map((e) => originalMirror.getField(e.simpleName).reflectee)
.where((e) => e != null)
.toList();
}
Map toMap() => {
'name': getDocgenObject(mirror, owningLibrary).docName,
'parameters': parameters
};
}