blob: ea87229c886ae62fdb68246d31946e2aedb3429e [file] [log] [blame]
// Copyright (c) 2012, 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.
/**
* This library serializes the Dart2Js AST into a compact and easy to use
* [Element] tree useful for code exploration tools such as DartDoc.
*/
library universe_serializer;
import '../../compiler/implementation/mirrors/mirrors.dart';
import '../../compiler/implementation/mirrors/mirrors_util.dart';
import '../../compiler/implementation/mirrors/dart2js_mirror.dart' as dart2js;
import '../../libraries.dart';
import 'dartdoc.dart';
String _stripUri(String uri) {
String prefix = "/dart/";
int start = uri.indexOf(prefix);
if (start != -1) {
return uri.substring(start + prefix.length);
} else {
return uri;
}
}
String _escapeId(String id) {
return id.replaceAll(new RegExp('[/]'), '#slash');
}
/**
* Base class for all elements in the AST.
*/
class Element {
/** Human readable type name for the node. */
final String kind;
/** Human readable name for the element. */
final String name;
/** Id for the node that is unique within its parent's children. */
final String id;
/** Raw text of the comment associated with the Element if any. */
final String comment;
/** Raw html comment for the Element from MDN. */
String mdnCommentHtml;
/**
* The URL to the page on MDN that content was pulled from for the current
* type being documented. Will be `null` if the type doesn't use any MDN
* content.
*/
String mdnUrl;
/** Children of the node. */
List<Element> children;
/** Whether the element is private. */
final bool isPrivate;
/**
* Uri containing the definition of the element.
*/
String uri;
/**
* Line in the original source file that starts the definition of the element.
*/
String line;
// TODO(jacobr): refactor the code so that lookupMdnComment does not need to
// be passed to every Element constructor.
Element(Mirror mirror, this.kind, this.name, String id, this.comment,
MdnComment lookupMdnComment(Mirror))
: line = mirror.location.line.toString(),
id = _escapeId(id),
isPrivate = _optionalBool(mirror.isPrivate),
uri = _stripUri(mirror.location.sourceUri.toString()) {
if (lookupMdnComment != null) {
var mdnComment = lookupMdnComment(mirror);
if (mdnComment != null) {
mdnCommentHtml = mdnComment.mdnComment;
mdnUrl = mdnComment.mdnUrl;
}
}
}
void addChild(Element child) {
if (children == null) {
children = <Element>[];
}
children.add(child);
}
/**
* Remove all URIs that exactly match the parent node's URI.
* This reduces output file size by about 20%.
*/
void stripDuplicateUris(String parentUri, parentLine) {
if (children != null) {
for (var child in children) {
child.stripDuplicateUris(uri, line);
}
}
if (parentUri == uri) {
uri = null;
}
if (line == parentLine) {
line = null;
}
}
}
/**
* Converts false to null. Useful as the serialization scheme we use
* omits null values.
*/
bool _optionalBool(bool value) => value == true ? true : null;
Reference _optionalReference(Mirror mirror) {
return (mirror != null && mirror.simpleName != "Dynamic_" &&
mirror.simpleName != "dynamic") ?
new Reference(mirror) : null;
}
/**
* Helper class to track what members of a library should be included.
*/
class LibrarySubset {
final LibraryMirror library;
Set<String> includedChildren;
LibrarySubset(this.library) : includedChildren = new Set<String>();
}
/**
* [Element] describing a Dart library.
*/
class LibraryElement extends Element {
/**
* Partial versions of LibraryElements containing classes that are extended
* or implemented by classes in this library.
*/
List<LibraryElement> dependencies;
/**
* Construct a LibraryElement from a [mirror].
*
* If [includedChildren] is specified, only elements matching names in
* [includedChildren] are included and no dependencies are included.
* [lookupMdnComment] is an optional function that returns the MDN
* documentation for elements. [dependencies] is an optional map
* tracking all classes dependend on by this [ClassElement].
*/
LibraryElement(LibraryMirror mirror,
{MdnComment lookupMdnComment(Mirror), Set<String> includedChildren})
: super(mirror, 'library', _libraryName(mirror), mirror.displayName,
computeComment(mirror), lookupMdnComment) {
var requiredDependencies;
// We don't need to track our required dependencies when generating a
// filtered version of this library which will be used as a dependency for
// another library.
if (includedChildren == null)
requiredDependencies = new Map<String, LibrarySubset>();
mirror.functions.forEach((childName, childMirror) {
if (includedChildren == null || includedChildren.contains(childName))
addChild(new MethodElement(childName, childMirror, lookupMdnComment));
});
mirror.getters.forEach((childName, childMirror) {
if (includedChildren == null || includedChildren.contains(childName))
addChild(new GetterElement(childName, childMirror, lookupMdnComment));
});
mirror.variables.forEach((childName, childMirror) {
if (includedChildren == null || includedChildren.contains(childName))
addChild(new VariableElement(childName, childMirror, lookupMdnComment));
});
mirror.classes.forEach((className, classMirror) {
if (includedChildren == null || includedChildren.contains(className)) {
if (classMirror is TypedefMirror) {
addChild(new TypedefElement(className, classMirror));
} else {
addChild(new ClassElement(classMirror,
dependencies: requiredDependencies,
lookupMdnComment: lookupMdnComment));
}
}
});
if (requiredDependencies != null && !requiredDependencies.isEmpty) {
dependencies = requiredDependencies.values.map((librarySubset) =>
new LibraryElement(
librarySubset.library,
lookupMdnComment: lookupMdnComment,
includedChildren: librarySubset.includedChildren)).toList();
}
}
static String _libraryName(LibraryMirror mirror) {
if (mirror.uri.scheme == 'file') {
// TODO(jacobr): this is a hack. Remove once these libraries are removed
// from the sdk.
var uri = mirror.uri;
var path = uri.path;
var pattern = new RegExp(r'[\\/]dart[\\/]pkg[\\/]([^\\/]+)[\\/]lib[\\/](.+)$');
var match = pattern.firstMatch(path);
var package;
if (match != null) {
package = match.group(1);
path = match.group(2);
}
// TODO(jacobr): add a second pattern for a more typical pub environment.
if (package != null) {
return 'package:$package/$path';
} else {
// TODO(jacobr): this is a lousy fallback.
print("Unable to determine package for $path.");
return mirror.uri.toString();
}
} else {
return mirror.uri.toString();
}
}
void stripDuplicateUris(String parentUri, parentLine) {
super.stripDuplicateUris(parentUri, parentLine);
if (dependencies != null) {
for (var child in dependencies) {
child.stripDuplicateUris(null, null);
}
}
}
}
/**
* Returns whether the class implements or extends [Error] or [Exception].
*/
bool _isThrowable(ClassMirror mirror) {
if (mirror.library.uri.toString() == 'dart:core' &&
mirror.simpleName == 'Error' || mirror.simpleName == 'Exception')
return true;
if (mirror.superclass != null && _isThrowable(mirror.superclass))
return true;
return mirror.superinterfaces.any(_isThrowable);
}
/**
* [Element] describing a Dart class.
*/
class ClassElement extends Element {
/** Base class.*/
final Reference superclass;
/** Whether the class is abstract. */
final bool isAbstract;
/** Interfaces the class implements. */
List<Reference> interfaces;
/** Whether the class implements or extends [Error] or [Exception]. */
bool isThrowable;
/**
* Constructs a [ClassElement] from a [ClassMirror].
*
* [dependencies] is an optional map updated as a side effect of running
* this constructor that tracks what classes from other libraries are
* dependencies of classes in this library. A class is considered a
* dependency if it implements or extends another class.
* [lookupMdnComment] is an optional function that returns the MDN
* documentation for elements. [dependencies] is an optional map
* tracking all classes dependend on by this [ClassElement].
*/
ClassElement(ClassMirror mirror,
{Map<String, LibrarySubset> dependencies,
MdnComment lookupMdnComment(Mirror)})
: super(mirror, 'class', mirror.simpleName, mirror.simpleName, computeComment(mirror),
lookupMdnComment),
superclass = _optionalReference(mirror.superclass),
isAbstract = _optionalBool(mirror.isAbstract),
isThrowable = _optionalBool(_isThrowable(mirror)){
addCrossLibraryDependencies(clazz) {
if (clazz == null) return;
if (mirror.library != clazz.library) {
var libraryStub = dependencies.putIfAbsent(clazz.library.simpleName,
() => new LibrarySubset(clazz.library));
libraryStub.includedChildren.add(clazz.simpleName);
}
for (var interface in clazz.superinterfaces) {
addCrossLibraryDependencies(interface);
}
addCrossLibraryDependencies(clazz.superclass);
}
if (dependencies != null) {
addCrossLibraryDependencies(mirror);
}
for (var interface in mirror.superinterfaces) {
if (this.interfaces == null) {
this.interfaces = <Reference>[];
}
this.interfaces.add(_optionalReference(interface));
}
mirror.methods.forEach((childName, childMirror) {
if (!childMirror.isConstructor && !childMirror.isGetter) {
addChild(new MethodElement(childName, childMirror, lookupMdnComment));
}
});
mirror.getters.forEach((childName, childMirror) {
addChild(new GetterElement(childName, childMirror, lookupMdnComment));
});
mirror.variables.forEach((childName, childMirror) {
addChild(new VariableElement(childName, childMirror,
lookupMdnComment));
});
mirror.constructors.forEach((constructorName, methodMirror) {
addChild(new MethodElement(constructorName, methodMirror,
lookupMdnComment, 'constructor'));
});
for (var typeVariable in mirror.originalDeclaration.typeVariables) {
addChild(new TypeParameterElement(typeVariable));
}
}
}
/**
* [Element] describing a getter.
*/
class GetterElement extends Element {
/** Type of the getter. */
final Reference ref;
final bool isStatic;
GetterElement(String name, MethodMirror mirror,
MdnComment lookupMdnComment(Mirror))
: super(mirror, 'property', mirror.simpleName, name, computeComment(mirror),
lookupMdnComment),
ref = _optionalReference(mirror.returnType),
isStatic = _optionalBool(mirror.isStatic);
}
/**
* [Element] describing a method which may be a regular method, a setter, or an
* operator.
*/
class MethodElement extends Element {
final Reference returnType;
final bool isSetter;
final bool isOperator;
final bool isStatic;
MethodElement(String name, MethodMirror mirror,
MdnComment lookupMdnComment(Mirror), [String kind = 'method'])
: super(mirror, kind, name, '$name${mirror.parameters.length}()',
computeComment(mirror), lookupMdnComment),
returnType = _optionalReference(mirror.returnType),
isSetter = _optionalBool(mirror.isSetter),
isOperator = _optionalBool(mirror.isOperator),
isStatic = _optionalBool(mirror.isStatic) {
for (var param in mirror.parameters) {
addChild(new ParameterElement(param));
}
}
}
/**
* Element describing a parameter.
*/
class ParameterElement extends Element {
/** Type of the parameter. */
final Reference ref;
/**
* Returns the default value for this parameter.
*/
final String defaultValue;
/**
* Is this parameter optional?
*/
final bool isOptional;
/**
* Is this parameter named?
*/
final bool isNamed;
/**
* Returns the initialized field, if this parameter is an initializing formal.
*/
final Reference initializedField;
ParameterElement(ParameterMirror mirror)
: super(mirror, 'param', mirror.simpleName, mirror.simpleName, null,
null),
ref = _optionalReference(mirror.type),
isOptional = _optionalBool(mirror.isOptional),
defaultValue = mirror.defaultValue,
isNamed = _optionalBool(mirror.isNamed),
initializedField = _optionalReference(mirror.initializedField) {
if (mirror.type is FunctionTypeMirror) {
addChild(new FunctionTypeElement(mirror.type));
}
}
}
class FunctionTypeElement extends Element {
final Reference returnType;
FunctionTypeElement(FunctionTypeMirror mirror)
: super(mirror, 'functiontype', mirror.simpleName, mirror.simpleName, null, null),
returnType = _optionalReference(mirror.returnType) {
for (var param in mirror.parameters) {
addChild(new ParameterElement(param));
}
// TODO(jacobr): can a FunctionTypeElement really have type variables?
for (var typeVariable in mirror.originalDeclaration.typeVariables) {
addChild(new TypeParameterElement(typeVariable));
}
}
}
/**
* Element describing a generic type parameter.
*/
class TypeParameterElement extends Element {
/**
* Upper bound for the parameter.
*
* In the following code sample, [:Bar:] is an upper bound:
* [: class Bar<T extends Foo> { } :]
*/
final Reference upperBound;
TypeParameterElement(TypeMirror mirror)
: super(mirror, 'typeparam', mirror.simpleName, mirror.simpleName, null,
null),
upperBound = mirror.upperBound != null && !mirror.upperBound.isObject ?
new Reference(mirror.upperBound) : null;
}
/**
* Element describing a variable.
*/
class VariableElement extends Element {
/** Type of the variable. */
final Reference ref;
/** Whether the variable is static. */
final bool isStatic;
/** Whether the variable is final. */
final bool isFinal;
VariableElement(String name, VariableMirror mirror,
MdnComment lookupMdnComment(Mirror))
: super(mirror, 'variable', mirror.simpleName, name,
computeComment(mirror), lookupMdnComment),
ref = _optionalReference(mirror.type),
isStatic = _optionalBool(mirror.isStatic),
isFinal = _optionalBool(mirror.isFinal);
}
/**
* Element describing a typedef.
*/
class TypedefElement extends Element {
/** Return type of the typedef. */
final Reference returnType;
TypedefElement(String name, TypedefMirror mirror)
: super(mirror, 'typedef', mirror.simpleName, name,
computeComment(mirror), null),
returnType = _optionalReference(mirror.value.returnType) {
for (var param in mirror.value.parameters) {
addChild(new ParameterElement(param));
}
for (var typeVariable in mirror.originalDeclaration.typeVariables) {
addChild(new TypeParameterElement(typeVariable));
}
}
}
/**
* Reference to an Element with type argument if the reference is parameterized.
*/
class Reference {
final String name;
final String refId;
List<Reference> arguments;
Reference(Mirror mirror)
: name = mirror.displayName,
refId = getId(mirror) {
if (mirror is ClassMirror) {
if (mirror is !TypedefMirror && !mirror.typeArguments.isEmpty) {
arguments = <Reference>[];
for (var typeArg in mirror.typeArguments) {
arguments.add(_optionalReference(typeArg));
}
}
}
}
// TODO(jacobr): compute the referenceId correctly for the general case so
// that this method can work with all element types not just LibraryElements.
Reference.fromElement(LibraryElement e) : name = e.name, refId = e.id;
static String getId(Mirror mirror) {
String id = _escapeId(mirror.simpleName);
if (mirror.owner != null) {
id = '${getId(mirror.owner)}/$id';
}
return id;
}
}