// Copyright (c) 2013, 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. /** * A library for extracting the documentation from the various HTML libraries * ([dart:html], [dart:svg], [dart:web_audio], [dart:indexed_db]) and saving * those documentation comments to a JSON file. */ library docs; import '../../../../sdk/lib/_internal/dartdoc/lib/src/dart2js_mirrors.dart'; import '../../../../pkg/compiler/lib/src/mirrors/source_mirrors.dart'; import '../../../../pkg/compiler/lib/src/mirrors/mirrors_util.dart'; import '../../../../sdk/lib/_internal/dartdoc/lib/dartdoc.dart'; import '../../../../sdk/lib/_internal/dartdoc/lib/src/json_serializer.dart'; import '../../../../utils/apidoc/lib/metadata.dart'; import 'dart:async'; import 'dart:io'; /// The various HTML libraries. const List HTML_LIBRARY_NAMES = const [ 'dart:html', 'dart:indexed_db', 'dart:svg', 'dart:web_audio', 'dart:web_gl', 'dart:web_sql' ]; /** * Converts the libraries in [HTML_LIBRARY_NAMES] to a json file at [jsonPath] * given the library path at [libUri]. * * The json output looks like: * { * $library_name: { * $interface_name: { * comment: "$comment" * members: { * $member: [ * [$comment1line1, * $comment1line2, * ...], * ... * ], * ... * } * }, * ... * }, * ... * } * * Completes to true if any errors were encountered, false otherwise. */ Future convert(String libUri, String jsonPath) { var paths = []; for (var libraryName in HTML_LIBRARY_NAMES) { paths.add(libraryName); } return analyze(paths, libUri, options: ['--preserve-comments']) .then((MirrorSystem mirrors) { var convertedJson = _generateJsonFromLibraries(mirrors); return _exportJsonToFile(convertedJson, jsonPath); }); } Future _exportJsonToFile(Map convertedJson, String jsonPath) { return new Future.sync(() { final jsonFile = new File(jsonPath); var writeJson = prettySerialize(convertedJson); var outputStream = jsonFile.openWrite(); outputStream.writeln(writeJson); outputStream.close(); return outputStream.done.then((_) => false); }); } Map _generateJsonFromLibraries(MirrorSystem mirrors) { var convertedJson = {}; // Sort the libraries by name (not key). var sortedLibraries = new List.from(mirrors.libraries.values .where((e) => HTML_LIBRARY_NAMES.indexOf(e.uri.toString()) >= 0)) ..sort((x, y) => x.uri .toString() .toUpperCase() .compareTo(y.uri.toString().toUpperCase())); for (LibraryMirror libMirror in sortedLibraries) { print('Extracting documentation from ${libMirror.simpleName}.'); var libraryJson = {}; var sortedClasses = _sortAndFilterMirrors( classesOf(libMirror.declarations).toList(), ignoreDocsEditable: true); for (ClassMirror classMirror in sortedClasses) { print(' class: $classMirror'); var classJson = {}; var sortedMembers = _sortAndFilterMirrors(membersOf(classMirror.declarations).toList()); var membersJson = {}; for (var memberMirror in sortedMembers) { print(' member: $memberMirror'); var memberDomName = domNames(memberMirror)[0]; var memberComment = _splitCommentsByNewline( computeUntrimmedCommentAsList(memberMirror)); // Remove interface name from Dom Name. if (memberDomName.indexOf('.') >= 0) { memberDomName = memberDomName.substring(memberDomName.indexOf('.') + 1); } if (!memberComment.isEmpty) { membersJson.putIfAbsent(memberDomName, () => memberComment); } } // Only include the comment if DocsEditable is set. var classComment = _splitCommentsByNewline(computeUntrimmedCommentAsList(classMirror)); if (!classComment.isEmpty && findMetadata(classMirror.metadata, 'DocsEditable') != null) { classJson.putIfAbsent('comment', () => classComment); } if (!membersJson.isEmpty) { classJson.putIfAbsent('members', () => membersJson); } if (!classJson.isEmpty) { libraryJson.putIfAbsent(domNames(classMirror)[0], () => classJson); } } if (!libraryJson.isEmpty) { convertedJson.putIfAbsent(nameOf(libMirror), () => libraryJson); } } return convertedJson; } /// Filter out mirrors that are private, or which are not part of this docs /// process. That is, ones without the DocsEditable annotation. /// If [ignoreDocsEditable] is true, relax the restriction on @DocsEditable(). /// This is to account for classes that are defined in a template, but whose /// members are generated. List _sortAndFilterMirrors(List mirrors, {ignoreDocsEditable: false}) { var filteredMirrors = mirrors .where((DeclarationMirror c) => !domNames(c).isEmpty && !displayName(c).startsWith('_') && (!ignoreDocsEditable ? (findMetadata(c.metadata, 'DocsEditable') != null) : true)) .toList(); filteredMirrors.sort((x, y) => domNames(x)[0].toUpperCase().compareTo(domNames(y)[0].toUpperCase())); return filteredMirrors; } List _splitCommentsByNewline(List comments) { var out = []; comments.forEach((c) { out.addAll(c.split(new RegExp('\n'))); }); return out; } /// Given the class mirror, returns the names found or an empty list. List domNames(DeclarationMirror mirror) { var domNameMetadata = findMetadata(mirror.metadata, 'DomName'); if (domNameMetadata != null) { var domNames = []; var tags = domNameMetadata.getField(#name); for (var s in tags.reflectee.split(',')) { domNames.add(s.trim()); } if (domNames.length == 1 && domNames[0] == 'none') return []; return domNames; } else { return []; } }