| // 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 'dartdoc.dart'; |
| |
| // TODO(rnystrom): Use "package:" URL (#4968). |
| import 'package:path/path.dart' as path; |
| import '../../compiler/implementation/mirrors/dart2js_mirror.dart' as dart2js; |
| import '../../compiler/implementation/mirrors/mirrors.dart'; |
| import '../../compiler/implementation/mirrors/mirrors_util.dart'; |
| import '../../libraries.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.simpleName, |
| 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 uriPath = uri.path; |
| |
| var parts = path.split(uriPath); |
| |
| // Find either pkg/ or packages/ |
| var pkgDir = parts.lastIndexOf('pkg'); |
| var packageDir = parts.lastIndexOf('packages'); |
| |
| if (pkgDir >= 0) { |
| packageDir = pkgDir; |
| } |
| |
| var libDir = parts.lastIndexOf('lib'); |
| var rest = parts.sublist(libDir + 1); |
| |
| // If there's no lib, we can't find the package. |
| if (libDir < 0 || libDir < packageDir) { |
| // TODO(jacobr): this is a lousy fallback. |
| print("Unable to determine package for $uriPath."); |
| return mirror.uri.toString(); |
| } else if (packageDir >= 0 && rest.length >= 1) { |
| // For URI: foo/bar/packages/widget/lib/sprocket.dart will return: |
| // 'package:widget/sprocket.dart' |
| return 'package:${parts[packageDir + 1]}/${rest.join('/')}'; |
| } |
| } 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 = displayName(mirror), |
| 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; |
| } |
| } |