| Index: utils/dartdoc/dartdoc.dart
|
| diff --git a/utils/dartdoc/dartdoc.dart b/utils/dartdoc/dartdoc.dart
|
| deleted file mode 100644
|
| index 417234949756f9bc645b8045a65965ef773379be..0000000000000000000000000000000000000000
|
| --- a/utils/dartdoc/dartdoc.dart
|
| +++ /dev/null
|
| @@ -1,1218 +0,0 @@
|
| -// Copyright (c) 2011, 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.
|
| -
|
| -/**
|
| - * To use it, from this directory, run:
|
| - *
|
| - * $ ./dartdoc <path to .dart file>
|
| - *
|
| - * This will create a "docs" directory with the docs for your libraries. To
|
| - * create these beautiful docs, dartdoc parses your library and every library
|
| - * it imports (recursively). From each library, it parses all classes and
|
| - * members, finds the associated doc comments and builds crosslinked docs from
|
| - * them.
|
| - */
|
| -#library('dartdoc');
|
| -
|
| -#import('dart:io');
|
| -#import('dart:json');
|
| -#import('../../frog/lang.dart');
|
| -#import('../../frog/file_system.dart');
|
| -#import('../../frog/file_system_vm.dart');
|
| -#import('classify.dart');
|
| -#import('markdown.dart', prefix: 'md');
|
| -
|
| -#source('comment_map.dart');
|
| -#source('utils.dart');
|
| -
|
| -/** Path to generate HTML files into. */
|
| -final _outdir = 'docs';
|
| -
|
| -/**
|
| - * Generates completely static HTML containing everything you need to browse
|
| - * the docs. The only client side behavior is trivial stuff like syntax
|
| - * highlighting code.
|
| - */
|
| -final MODE_STATIC = 0;
|
| -
|
| -/**
|
| - * Generated docs do not include baked HTML navigation. Instead, a single
|
| - * `nav.json` file is created and the appropriate navigation is generated
|
| - * client-side by parsing that and building HTML.
|
| - *
|
| - * This dramatically reduces the generated size of the HTML since a large
|
| - * fraction of each static page is just redundant navigation links.
|
| - *
|
| - * In this mode, the browser will do a XHR for nav.json which means that to
|
| - * preview docs locally, you will need to enable requesting file:// links in
|
| - * your browser or run a little local server like `python -m SimpleHTTPServer`.
|
| - */
|
| -final MODE_LIVE_NAV = 1;
|
| -
|
| -/**
|
| - * Run this from the `utils/dartdoc` directory.
|
| - */
|
| -void main() {
|
| - final args = new Options().arguments;
|
| -
|
| - // The entrypoint of the library to generate docs for.
|
| - final entrypoint = args[args.length - 1];
|
| -
|
| - // Parse the dartdoc options.
|
| - bool includeSource = true;
|
| - var mode = MODE_LIVE_NAV;
|
| -
|
| - for (int i = 2; i < args.length - 1; i++) {
|
| - final arg = args[i];
|
| - switch (arg) {
|
| - case '--no-code':
|
| - includeSource = false;
|
| - break;
|
| -
|
| - case '--mode=static':
|
| - mode = MODE_STATIC;
|
| - break;
|
| -
|
| - case '--mode=live-nav':
|
| - mode = MODE_LIVE_NAV;
|
| - break;
|
| -
|
| - default:
|
| - print('Unknown option: $arg');
|
| - }
|
| - }
|
| -
|
| - final files = new VMFileSystem();
|
| - parseOptions('../../frog', ['', '', '--libdir=../../frog/lib'], files);
|
| - initializeWorld(files);
|
| -
|
| - var dartdoc;
|
| - final elapsed = time(() {
|
| - dartdoc = new Dartdoc();
|
| - dartdoc.includeSource = includeSource;
|
| - dartdoc.mode = mode;
|
| -
|
| - dartdoc.document(entrypoint);
|
| - });
|
| -
|
| - print('Documented ${dartdoc._totalLibraries} libraries, ' +
|
| - '${dartdoc._totalTypes} types, and ' +
|
| - '${dartdoc._totalMembers} members in ${elapsed}msec.');
|
| -}
|
| -
|
| -class Dartdoc {
|
| - /** Set to `false` to not include the source code in the generated docs. */
|
| - bool includeSource = true;
|
| -
|
| - /**
|
| - * Dartdoc can generate docs in a few different ways based on how dynamic you
|
| - * want the client-side behavior to be. The value for this should be one of
|
| - * the `MODE_` constants.
|
| - */
|
| - int mode = MODE_LIVE_NAV;
|
| -
|
| - /**
|
| - * The title used for the overall generated output. Set this to change it.
|
| - */
|
| - String mainTitle = 'Dart Documentation';
|
| -
|
| - /**
|
| - * The URL that the Dart logo links to. Defaults "index.html", the main
|
| - * page for the generated docs, but can be anything.
|
| - */
|
| - String mainUrl = 'index.html';
|
| -
|
| - /**
|
| - * The Google Custom Search ID that should be used for the search box. If
|
| - * this is `null` then no search box will be shown.
|
| - */
|
| - String searchEngineId = null;
|
| -
|
| - /* The URL that the embedded search results should be displayed on. */
|
| - String searchResultsUrl = 'results.html';
|
| -
|
| - /** Set this to add footer text to each generated page. */
|
| - String footerText = '';
|
| -
|
| - /**
|
| - * From exposes the set of libraries in `world.libraries`. That maps library
|
| - * *keys* to [Library] objects. The keys are *not* exactly the same as their
|
| - * names. This means if we order by key, we won't actually have them sorted
|
| - * correctly. This list contains the libraries in correct order by their
|
| - * *name*.
|
| - */
|
| - List<Library> _sortedLibraries;
|
| -
|
| - CommentMap _comments;
|
| -
|
| - /** The library that we're currently generating docs for. */
|
| - Library _currentLibrary;
|
| -
|
| - /** The type that we're currently generating docs for. */
|
| - Type _currentType;
|
| -
|
| - /** The member that we're currently generating docs for. */
|
| - Member _currentMember;
|
| -
|
| - /** The path to the file currently being written to, relative to [outdir]. */
|
| - String _filePath;
|
| -
|
| - /** The file currently being written to. */
|
| - StringBuffer _file;
|
| -
|
| - int _totalLibraries = 0;
|
| - int _totalTypes = 0;
|
| - int _totalMembers = 0;
|
| -
|
| - Dartdoc()
|
| - : _comments = new CommentMap() {
|
| - // Patch in support for [:...:]-style code to the markdown parser.
|
| - // TODO(rnystrom): Markdown already has syntax for this. Phase this out?
|
| - md.InlineParser.syntaxes.insertRange(0, 1,
|
| - new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]'));
|
| -
|
| - md.setImplicitLinkResolver((name) => resolveNameReference(name,
|
| - library: _currentLibrary, type: _currentType,
|
| - member: _currentMember));
|
| - }
|
| -
|
| - void document(String entrypoint) {
|
| - var oldDietParse = options.dietParse;
|
| - try {
|
| - options.dietParse = true;
|
| -
|
| - // Handle the built-in entrypoints.
|
| - switch (entrypoint) {
|
| - case 'corelib':
|
| - world.getOrAddLibrary('dart:core');
|
| - world.getOrAddLibrary('dart:coreimpl');
|
| - world.getOrAddLibrary('dart:json');
|
| - world.getOrAddLibrary('dart:isolate');
|
| - world.process();
|
| - break;
|
| -
|
| - case 'dom':
|
| - world.getOrAddLibrary('dart:core');
|
| - world.getOrAddLibrary('dart:coreimpl');
|
| - world.getOrAddLibrary('dart:json');
|
| - world.getOrAddLibrary('dart:dom');
|
| - world.getOrAddLibrary('dart:isolate');
|
| - world.process();
|
| - break;
|
| -
|
| - case 'html':
|
| - world.getOrAddLibrary('dart:core');
|
| - world.getOrAddLibrary('dart:coreimpl');
|
| - world.getOrAddLibrary('dart:json');
|
| - world.getOrAddLibrary('dart:dom');
|
| - world.getOrAddLibrary('dart:html');
|
| - world.getOrAddLibrary('dart:isolate');
|
| - world.process();
|
| - break;
|
| -
|
| - default:
|
| - // Normal entrypoint script.
|
| - world.processDartScript(entrypoint);
|
| - }
|
| -
|
| - world.resolveAll();
|
| -
|
| - // Sort the libraries by name (not key).
|
| - _sortedLibraries = world.libraries.getValues();
|
| - _sortedLibraries.sort((a, b) {
|
| - return a.name.toUpperCase().compareTo(b.name.toUpperCase());
|
| - });
|
| -
|
| - // Generate the docs.
|
| - if (mode == MODE_LIVE_NAV) docNavigationJson();
|
| -
|
| - docIndex();
|
| - for (final library in _sortedLibraries) {
|
| - docLibrary(library);
|
| - }
|
| - } finally {
|
| - options.dietParse = oldDietParse;
|
| - }
|
| - }
|
| -
|
| - void startFile(String path) {
|
| - _filePath = path;
|
| - _file = new StringBuffer();
|
| - }
|
| -
|
| - void endFile() {
|
| - final outPath = '$_outdir/$_filePath';
|
| - final dir = new Directory(dirname(outPath));
|
| - if (!dir.existsSync()) {
|
| - dir.createSync();
|
| - }
|
| -
|
| - world.files.writeString(outPath, _file.toString());
|
| - _filePath = null;
|
| - _file = null;
|
| - }
|
| -
|
| - void write(String s) {
|
| - _file.add(s);
|
| - }
|
| -
|
| - void writeln(String s) {
|
| - write(s);
|
| - write('\n');
|
| - }
|
| -
|
| - /**
|
| - * Writes the page header with the given [title] and [breadcrumbs]. The
|
| - * breadcrumbs are an interleaved list of links and titles. If a link is null,
|
| - * then no link will be generated. For example, given:
|
| - *
|
| - * ['foo', 'foo.html', 'bar', null]
|
| - *
|
| - * It will output:
|
| - *
|
| - * <a href="foo.html">foo</a> › bar
|
| - */
|
| - void writeHeader(String title, List<String> breadcrumbs) {
|
| - write(
|
| - '''
|
| - <!DOCTYPE html>
|
| - <html>
|
| - <head>
|
| - ''');
|
| - writeHeadContents(title);
|
| -
|
| - // Add data attributes describing what the page documents.
|
| - var data = '';
|
| - if (_currentLibrary != null) {
|
| - data += ' data-library="${md.escapeHtml(_currentLibrary.name)}"';
|
| - }
|
| -
|
| - if (_currentType != null) {
|
| - data += ' data-type="${md.escapeHtml(typeName(_currentType))}"';
|
| - }
|
| -
|
| - write(
|
| - '''
|
| - </head>
|
| - <body$data>
|
| - <div class="page">
|
| - <div class="header">
|
| - ${a(mainUrl, '<div class="logo"></div>')}
|
| - ${a('index.html', mainTitle)}
|
| - ''');
|
| -
|
| - // Write the breadcrumb trail.
|
| - for (int i = 0; i < breadcrumbs.length; i += 2) {
|
| - if (breadcrumbs[i + 1] == null) {
|
| - write(' › ${breadcrumbs[i]}');
|
| - } else {
|
| - write(' › ${a(breadcrumbs[i + 1], breadcrumbs[i])}');
|
| - }
|
| - }
|
| -
|
| - if (searchEngineId != null) {
|
| - writeln(
|
| - '''
|
| - <form action="$searchResultsUrl" id="search-box">
|
| - <input type="hidden" name="cx" value="$searchEngineId">
|
| - <input type="hidden" name="ie" value="UTF-8">
|
| - <input type="hidden" name="hl" value="en">
|
| - <input type="search" name="q" id="q" autocomplete="off"
|
| - placeholder="Search">
|
| - </form>
|
| - ''');
|
| - }
|
| -
|
| - writeln('</div>');
|
| -
|
| - docNavigation();
|
| - writeln('<div class="content">');
|
| - }
|
| -
|
| - String get clientScript() {
|
| - switch (mode) {
|
| - case MODE_STATIC: return 'client-static';
|
| - case MODE_LIVE_NAV: return 'client-live-nav';
|
| - default: throw 'Unknown mode $mode.';
|
| - }
|
| - }
|
| -
|
| - void writeHeadContents(String title) {
|
| - writeln(
|
| - '''
|
| - <meta charset="utf-8">
|
| - <title>$title</title>
|
| - <link rel="stylesheet" type="text/css"
|
| - href="${relativePath('styles.css')}" />
|
| - <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700,800" rel="stylesheet" type="text/css">
|
| - <link rel="shortcut icon" href="${relativePath('favicon.ico')}" />
|
| - <script src="${relativePath('$clientScript.js')}"></script>
|
| - ''');
|
| - }
|
| -
|
| - void writeFooter() {
|
| - writeln(
|
| - '''
|
| - </div>
|
| - <div class="clear"></div>
|
| - </div>
|
| - <div class="footer">$footerText</div>
|
| - </body></html>
|
| - ''');
|
| - }
|
| -
|
| - void docIndex() {
|
| - startFile('index.html');
|
| -
|
| - writeHeader(mainTitle, []);
|
| -
|
| - writeln('<h2>$mainTitle</h2>');
|
| - writeln('<h3>Libraries</h3>');
|
| -
|
| - for (final library in _sortedLibraries) {
|
| - docIndexLibrary(library);
|
| - }
|
| -
|
| - writeFooter();
|
| - endFile();
|
| - }
|
| -
|
| - void docIndexLibrary(Library library) {
|
| - writeln('<h4>${a(libraryUrl(library), library.name)}</h4>');
|
| - }
|
| -
|
| - /**
|
| - * Walks the libraries and creates a JSON object containing the data needed
|
| - * to generate navigation for them.
|
| - */
|
| - void docNavigationJson() {
|
| - startFile('nav.json');
|
| -
|
| - final libraries = {};
|
| -
|
| - for (final library in _sortedLibraries) {
|
| - docLibraryNavigationJson(library, libraries);
|
| - }
|
| -
|
| - writeln(JSON.stringify(libraries));
|
| - endFile();
|
| - }
|
| -
|
| - void docLibraryNavigationJson(Library library, Map libraries) {
|
| - final types = [];
|
| -
|
| - for (final type in orderByName(library.types)) {
|
| - if (type.isTop) continue;
|
| - if (type.name.startsWith('_')) continue;
|
| -
|
| - final kind = type.isClass ? 'class' : 'interface';
|
| - final url = typeUrl(type);
|
| - types.add({ 'name': typeName(type), 'kind': kind, 'url': url });
|
| - }
|
| -
|
| - libraries[library.name] = types;
|
| - }
|
| -
|
| - void docNavigation() {
|
| - writeln(
|
| - '''
|
| - <div class="nav">
|
| - ''');
|
| -
|
| - if (mode == MODE_STATIC) {
|
| - for (final library in _sortedLibraries) {
|
| - write('<h2><div class="icon-library"></div>');
|
| -
|
| - if ((_currentLibrary == library) && (_currentType == null)) {
|
| - write('<strong>${library.name}</strong>');
|
| - } else {
|
| - write('${a(libraryUrl(library), library.name)}');
|
| - }
|
| - write('</h2>');
|
| -
|
| - // Only expand classes in navigation for current library.
|
| - if (_currentLibrary == library) docLibraryNavigation(library);
|
| - }
|
| - }
|
| -
|
| - writeln('</div>');
|
| - }
|
| -
|
| - /** Writes the navigation for the types contained by the given library. */
|
| - void docLibraryNavigation(Library library) {
|
| - // Show the exception types separately.
|
| - final types = <Type>[];
|
| - final exceptions = <Type>[];
|
| -
|
| - for (final type in orderByName(library.types)) {
|
| - if (type.isTop) continue;
|
| - if (type.name.startsWith('_')) continue;
|
| -
|
| - if (type.name.endsWith('Exception')) {
|
| - exceptions.add(type);
|
| - } else {
|
| - types.add(type);
|
| - }
|
| - }
|
| -
|
| - if ((types.length == 0) && (exceptions.length == 0)) return;
|
| -
|
| - writeln('<ul class="icon">');
|
| - types.forEach(docTypeNavigation);
|
| - exceptions.forEach(docTypeNavigation);
|
| - writeln('</ul>');
|
| - }
|
| -
|
| - /** Writes a linked navigation list item for the given type. */
|
| - void docTypeNavigation(Type type) {
|
| - var icon = 'interface';
|
| - if (type.name.endsWith('Exception')) {
|
| - icon = 'exception';
|
| - } else if (type.isClass) {
|
| - icon = 'class';
|
| - }
|
| -
|
| - write('<li>');
|
| - if (_currentType == type) {
|
| - write(
|
| - '<div class="icon-$icon"></div><strong>${typeName(type)}</strong>');
|
| - } else {
|
| - write(a(typeUrl(type),
|
| - '<div class="icon-$icon"></div>${typeName(type)}'));
|
| - }
|
| - writeln('</li>');
|
| - }
|
| -
|
| - void docLibrary(Library library) {
|
| - _totalLibraries++;
|
| - _currentLibrary = library;
|
| - _currentType = null;
|
| -
|
| - startFile(libraryUrl(library));
|
| - writeHeader(library.name, [library.name, libraryUrl(library)]);
|
| - writeln('<h2>Library <strong>${library.name}</strong></h2>');
|
| -
|
| - // Look for a comment for the entire library.
|
| - final comment = _comments.findLibrary(library.baseSource);
|
| - if (comment != null) {
|
| - final html = md.markdownToHtml(comment);
|
| - writeln('<div class="doc">$html</div>');
|
| - }
|
| -
|
| - // Document the top-level members.
|
| - docMembers(library.topType);
|
| -
|
| - // Document the types.
|
| - final classes = <Type>[];
|
| - final interfaces = <Type>[];
|
| - final exceptions = <Type>[];
|
| -
|
| - for (final type in orderByName(library.types)) {
|
| - if (type.isTop) continue;
|
| - if (type.name.startsWith('_')) continue;
|
| -
|
| - if (type.name.endsWith('Exception')) {
|
| - exceptions.add(type);
|
| - } else if (type.isClass) {
|
| - classes.add(type);
|
| - } else {
|
| - interfaces.add(type);
|
| - }
|
| - }
|
| -
|
| - docTypes(classes, 'Classes');
|
| - docTypes(interfaces, 'Interfaces');
|
| - docTypes(exceptions, 'Exceptions');
|
| -
|
| - writeFooter();
|
| - endFile();
|
| -
|
| - for (final type in library.types.getValues()) {
|
| - if (!type.isTop) docType(type);
|
| - }
|
| - }
|
| -
|
| - void docTypes(List<Type> types, String header) {
|
| - if (types.length == 0) return;
|
| -
|
| - writeln('<h3>$header</h3>');
|
| -
|
| - for (final type in types) {
|
| - writeln(
|
| - '''
|
| - <div class="type">
|
| - <h4>
|
| - ${a(typeUrl(type), "<strong>${typeName(type)}</strong>")}
|
| - </h4>
|
| - </div>
|
| - ''');
|
| - }
|
| - }
|
| -
|
| - void docType(Type type) {
|
| - _totalTypes++;
|
| - _currentType = type;
|
| -
|
| - startFile(typeUrl(type));
|
| -
|
| - final typeTitle =
|
| - '${type.isClass ? "Class" : "Interface"} ${typeName(type)}';
|
| - writeHeader('Library ${type.library.name} / $typeTitle',
|
| - [type.library.name, libraryUrl(type.library),
|
| - typeName(type), typeUrl(type)]);
|
| - writeln(
|
| - '''
|
| - <h2>${type.isClass ? "Class" : "Interface"}
|
| - <strong>${typeName(type, showBounds: true)}</strong></h2>
|
| - ''');
|
| -
|
| - docCode(type.span, getTypeComment(type));
|
| - docInheritance(type);
|
| - docConstructors(type);
|
| - docMembers(type);
|
| -
|
| - writeTypeFooter();
|
| - writeFooter();
|
| - endFile();
|
| - }
|
| -
|
| - /** Override this to write additional content at the end of a type's page. */
|
| - void writeTypeFooter() {
|
| - // Do nothing.
|
| - }
|
| -
|
| - /**
|
| - * Writes an inline type span for the given type. This is a little box with
|
| - * an icon and the type's name. It's similar to how types appear in the
|
| - * navigation, but is suitable for inline (as opposed to in a `<ul>`) use.
|
| - */
|
| - void typeSpan(Type type) {
|
| - var icon = 'interface';
|
| - if (type.name.endsWith('Exception')) {
|
| - icon = 'exception';
|
| - } else if (type.isClass) {
|
| - icon = 'class';
|
| - }
|
| -
|
| - write('<span class="type-box"><span class="icon-$icon"></span>');
|
| - if (_currentType == type) {
|
| - write('<strong>${typeName(type)}</strong>');
|
| - } else {
|
| - write(a(typeUrl(type), typeName(type)));
|
| - }
|
| - write('</span>');
|
| - }
|
| -
|
| - /**
|
| - * Document the other types that touch [Type] in the inheritance hierarchy:
|
| - * subclasses, superclasses, subinterfaces, superinferfaces, and default
|
| - * class.
|
| - */
|
| - void docInheritance(Type type) {
|
| - // Don't show the inheritance details for Object. It doesn't have any base
|
| - // class (obviously) and it has too many subclasses to be useful.
|
| - if (type.isObject) return;
|
| -
|
| - // Writes an unordered list of references to types with an optional header.
|
| - listTypes(types, header) {
|
| - if (types == null) return;
|
| -
|
| - // Skip private types.
|
| - final publicTypes = types.filter((type) => !type.name.startsWith('_'));
|
| - if (publicTypes.length == 0) return;
|
| -
|
| - writeln('<h3>$header</h3>');
|
| - writeln('<p>');
|
| - bool first = true;
|
| - for (final type in publicTypes) {
|
| - if (!first) write(', ');
|
| - typeSpan(type);
|
| - first = false;
|
| - }
|
| - writeln('</p>');
|
| - }
|
| -
|
| - if (type.isClass) {
|
| - // Show the chain of superclasses.
|
| - if (!type.parent.isObject) {
|
| - final supertypes = [];
|
| - var thisType = type.parent;
|
| - // As a sanity check, only show up to five levels of nesting, otherwise
|
| - // the box starts to get hideous.
|
| - do {
|
| - supertypes.add(thisType);
|
| - thisType = thisType.parent;
|
| - } while (!thisType.isObject);
|
| -
|
| - writeln('<h3>Extends</h3>');
|
| - writeln('<p>');
|
| - for (var i = supertypes.length - 1; i >= 0; i--) {
|
| - typeSpan(supertypes[i]);
|
| - write(' > ');
|
| - }
|
| -
|
| - // Write this class.
|
| - typeSpan(type);
|
| - writeln('</p>');
|
| - }
|
| -
|
| - // Find the immediate declared subclasses (Type.subtypes includes many
|
| - // transitive subtypes).
|
| - final subtypes = [];
|
| - for (final subtype in type.subtypes) {
|
| - if (subtype.parent == type) subtypes.add(subtype);
|
| - }
|
| - subtypes.sort((a, b) => a.name.compareTo(b.name));
|
| -
|
| - listTypes(subtypes, 'Subclasses');
|
| - listTypes(type.interfaces, 'Implements');
|
| - } else {
|
| - // Show the default class.
|
| - if (type.genericType.defaultType != null) {
|
| - listTypes([type.genericType.defaultType], 'Default class');
|
| - }
|
| -
|
| - // List extended interfaces.
|
| - listTypes(type.interfaces, 'Extends');
|
| -
|
| - // List subinterfaces and implementing classes.
|
| - final subinterfaces = [];
|
| - final implementing = [];
|
| -
|
| - for (final subtype in type.subtypes) {
|
| - // We only want explicitly declared subinterfaces, so check that this
|
| - // type is a superinterface.
|
| - for (final supertype in subtype.interfaces) {
|
| - if (supertype == type) {
|
| - if (subtype.isClass) {
|
| - implementing.add(subtype);
|
| - } else {
|
| - subinterfaces.add(subtype);
|
| - }
|
| - break;
|
| - }
|
| - }
|
| - }
|
| -
|
| - listTypes(subinterfaces, 'Subinterfaces');
|
| - listTypes(implementing, 'Implemented by');
|
| - }
|
| - }
|
| -
|
| - /** Document the constructors for [Type], if any. */
|
| - void docConstructors(Type type) {
|
| - final names = type.constructors.getKeys().filter(
|
| - (name) => !name.startsWith('_'));
|
| -
|
| - if (names.length > 0) {
|
| - writeln('<h3>Constructors</h3>');
|
| - names.sort((x, y) => x.toUpperCase().compareTo(y.toUpperCase()));
|
| -
|
| - for (final name in names) {
|
| - docMethod(type, type.constructors[name], constructorName: name);
|
| - }
|
| - }
|
| - }
|
| -
|
| - void docMembers(Type type) {
|
| - // Collect the different kinds of members.
|
| - final staticMethods = [];
|
| - final staticFields = [];
|
| - final instanceMethods = [];
|
| - final instanceFields = [];
|
| -
|
| - for (final member in orderByName(type.members)) {
|
| - if (member.name.startsWith('_')) continue;
|
| -
|
| - final methods = member.isStatic ? staticMethods : instanceMethods;
|
| - final fields = member.isStatic ? staticFields : instanceFields;
|
| -
|
| - if (member.isProperty) {
|
| - if (member.canGet) methods.add(member.getter);
|
| - if (member.canSet) methods.add(member.setter);
|
| - } else if (member.isMethod) {
|
| - methods.add(member);
|
| - } else if (member.isField) {
|
| - fields.add(member);
|
| - }
|
| - }
|
| -
|
| - if (staticMethods.length > 0) {
|
| - final title = type.isTop ? 'Functions' : 'Static Methods';
|
| - writeln('<h3>$title</h3>');
|
| - for (final method in staticMethods) docMethod(type, method);
|
| - }
|
| -
|
| - if (staticFields.length > 0) {
|
| - final title = type.isTop ? 'Variables' : 'Static Fields';
|
| - writeln('<h3>$title</h3>');
|
| - for (final field in staticFields) docField(type, field);
|
| - }
|
| -
|
| - if (instanceMethods.length > 0) {
|
| - writeln('<h3>Methods</h3>');
|
| - for (final method in instanceMethods) docMethod(type, method);
|
| - }
|
| -
|
| - if (instanceFields.length > 0) {
|
| - writeln('<h3>Fields</h3>');
|
| - for (final field in instanceFields) docField(type, field);
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Documents the [method] in type [type]. Handles all kinds of methods
|
| - * including getters, setters, and constructors.
|
| - */
|
| - void docMethod(Type type, MethodMember method,
|
| - [String constructorName = null]) {
|
| - _totalMembers++;
|
| - _currentMember = method;
|
| -
|
| - writeln('<div class="method"><h4 id="${memberAnchor(method)}">');
|
| -
|
| - if (includeSource) {
|
| - writeln('<span class="show-code">Code</span>');
|
| - }
|
| -
|
| - if (method.isConstructor) {
|
| - write(method.isConst ? 'const ' : 'new ');
|
| - }
|
| -
|
| - if (constructorName == null) {
|
| - annotateType(type, method.returnType);
|
| - }
|
| -
|
| - // Translate specially-named methods: getters, setters, operators.
|
| - var name = method.name;
|
| - if (name.startsWith('get:')) {
|
| - // Getter.
|
| - name = 'get ${name.substring(4)}';
|
| - } else if (name.startsWith('set:')) {
|
| - // Setter.
|
| - name = 'set ${name.substring(4)}';
|
| - } else if (name == ':negate') {
|
| - // Dart uses 'negate' for prefix negate operators, not '!'.
|
| - name = 'operator negate';
|
| - } else if (name == ':call') {
|
| - name = 'operator call';
|
| - } else {
|
| - // See if it's an operator.
|
| - name = TokenKind.rawOperatorFromMethod(name);
|
| - if (name == null) {
|
| - name = method.name;
|
| - } else {
|
| - name = 'operator $name';
|
| - }
|
| - }
|
| -
|
| - write('<strong>$name</strong>');
|
| -
|
| - // Named constructors.
|
| - if (constructorName != null && constructorName != '') {
|
| - write('.');
|
| - write(constructorName);
|
| - }
|
| -
|
| - docParamList(type, method);
|
| -
|
| - write(''' <a class="anchor-link" href="#${memberAnchor(method)}"
|
| - title="Permalink to ${typeName(type)}.$name">#</a>''');
|
| - writeln('</h4>');
|
| -
|
| - docCode(method.span, getMethodComment(method), showCode: true);
|
| -
|
| - writeln('</div>');
|
| - }
|
| -
|
| - /** Documents the field [field] of type [type]. */
|
| - void docField(Type type, FieldMember field) {
|
| - _totalMembers++;
|
| - _currentMember = field;
|
| -
|
| - writeln('<div class="field"><h4 id="${memberAnchor(field)}">');
|
| -
|
| - if (includeSource) {
|
| - writeln('<span class="show-code">Code</span>');
|
| - }
|
| -
|
| - if (field.isFinal) {
|
| - write('final ');
|
| - } else if (field.type.name == 'Dynamic') {
|
| - write('var ');
|
| - }
|
| -
|
| - annotateType(type, field.type);
|
| - write(
|
| - '''
|
| - <strong>${field.name}</strong> <a class="anchor-link"
|
| - href="#${memberAnchor(field)}"
|
| - title="Permalink to ${typeName(type)}.${field.name}">#</a>
|
| - </h4>
|
| - ''');
|
| -
|
| - docCode(field.span, getFieldComment(field), showCode: true);
|
| - writeln('</div>');
|
| - }
|
| -
|
| - void docParamList(Type enclosingType, MethodMember member) {
|
| - write('(');
|
| - bool first = true;
|
| - bool inOptionals = false;
|
| - for (final parameter in member.parameters) {
|
| - if (!first) write(', ');
|
| -
|
| - if (!inOptionals && parameter.isOptional) {
|
| - write('[');
|
| - inOptionals = true;
|
| - }
|
| -
|
| - annotateType(enclosingType, parameter.type, parameter.name);
|
| -
|
| - // Show the default value for named optional parameters.
|
| - if (parameter.isOptional && parameter.hasDefaultValue) {
|
| - write(' = ');
|
| - // TODO(rnystrom): Using the definition text here is a bit cheap.
|
| - // We really should be pretty-printing the AST so that if you have:
|
| - // foo([arg = 1 + /* comment */ 2])
|
| - // the docs should just show:
|
| - // foo([arg = 1 + 2])
|
| - // For now, we'll assume you don't do that.
|
| - write(parameter.definition.value.span.text);
|
| - }
|
| -
|
| - first = false;
|
| - }
|
| -
|
| - if (inOptionals) write(']');
|
| - write(')');
|
| - }
|
| -
|
| - /**
|
| - * Documents the code contained within [span] with [comment]. If [showCode]
|
| - * is `true` (and [includeSource] is set), also includes the source code.
|
| - */
|
| - void docCode(SourceSpan span, String comment, [bool showCode = false]) {
|
| - writeln('<div class="doc">');
|
| - if (comment != null) {
|
| - writeln(comment);
|
| - }
|
| -
|
| - if (includeSource && showCode) {
|
| - writeln('<pre class="source">');
|
| - writeln(md.escapeHtml(unindentCode(span)));
|
| - writeln('</pre>');
|
| - }
|
| -
|
| - writeln('</div>');
|
| - }
|
| -
|
| - /** Get the doc comment associated with the given type. */
|
| - String getTypeComment(Type type) {
|
| - String comment = _comments.find(type.span);
|
| - if (comment == null) return null;
|
| - return md.markdownToHtml(comment);
|
| - }
|
| -
|
| - /** Get the doc comment associated with the given method. */
|
| - String getMethodComment(MethodMember method) {
|
| - String comment = _comments.find(method.span);
|
| - if (comment == null) return null;
|
| - return md.markdownToHtml(comment);
|
| - }
|
| -
|
| - /** Get the doc comment associated with the given field. */
|
| - String getFieldComment(FieldMember field) {
|
| - String comment = _comments.find(field.span);
|
| - if (comment == null) return null;
|
| - return md.markdownToHtml(comment);
|
| - }
|
| -
|
| - /**
|
| - * Converts [fullPath] which is understood to be a full path from the root of
|
| - * the generated docs to one relative to the current file.
|
| - */
|
| - String relativePath(String fullPath) {
|
| - // Don't make it relative if it's an absolute path.
|
| - if (isAbsolute(fullPath)) return fullPath;
|
| -
|
| - // TODO(rnystrom): Walks all the way up to root each time. Shouldn't do
|
| - // this if the paths overlap.
|
| - return repeat('../', countOccurrences(_filePath, '/')) + fullPath;
|
| - }
|
| -
|
| - /** Gets whether or not the given URL is absolute or relative. */
|
| - bool isAbsolute(String url) {
|
| - // TODO(rnystrom): Why don't we have a nice type in the platform for this?
|
| - // TODO(rnystrom): This is a bit hackish. We consider any URL that lacks
|
| - // a scheme to be relative.
|
| - return const RegExp(@'^\w+:').hasMatch(url);
|
| - }
|
| -
|
| - /** Gets the URL to the documentation for [library]. */
|
| - String libraryUrl(Library library) {
|
| - return '${sanitize(library.name)}.html';
|
| - }
|
| -
|
| - /** Gets the URL for the documentation for [type]. */
|
| - String typeUrl(Type type) {
|
| - if (type.isTop) return '${sanitize(type.library.name)}.html';
|
| - // Always get the generic type to strip off any type parameters or
|
| - // arguments. If the type isn't generic, genericType returns `this`, so it
|
| - // works for non-generic types too.
|
| - return '${sanitize(type.library.name)}/${type.genericType.name}.html';
|
| - }
|
| -
|
| - /** Gets the URL for the documentation for [member]. */
|
| - String memberUrl(Member member) {
|
| - final typeUrl = typeUrl(member.declaringType);
|
| - if (!member.isConstructor) return '$typeUrl#${member.name}';
|
| - if (member.constructorName == '') return '$typeUrl#new:${member.name}';
|
| - return '$typeUrl#new:${member.name}.${member.constructorName}';
|
| - }
|
| -
|
| - /** Gets the anchor id for the document for [member]. */
|
| - String memberAnchor(Member member) {
|
| - return '${member.name}';
|
| - }
|
| -
|
| - /**
|
| - * Creates a hyperlink. Handles turning the [href] into an appropriate
|
| - * relative path from the current file.
|
| - */
|
| - String a(String href, String contents, [String css]) {
|
| - // Mark outgoing external links, mainly so we can style them.
|
| - final rel = isAbsolute(href) ? ' ref="external"' : '';
|
| - final cssClass = css == null ? '' : ' class="$css"';
|
| - return '<a href="${relativePath(href)}"$cssClass$rel>$contents</a>';
|
| - }
|
| -
|
| - /**
|
| - * Writes a type annotation for the given type and (optional) parameter name.
|
| - */
|
| - annotateType(Type enclosingType, Type type, [String paramName = null]) {
|
| - // Don't bother explicitly displaying Dynamic.
|
| - if (type.isVar) {
|
| - if (paramName !== null) write(paramName);
|
| - return;
|
| - }
|
| -
|
| - // For parameters, handle non-typedefed function types.
|
| - if (paramName !== null) {
|
| - final call = type.getCallMethod();
|
| - if (call != null) {
|
| - annotateType(enclosingType, call.returnType);
|
| - write(paramName);
|
| -
|
| - docParamList(enclosingType, call);
|
| - return;
|
| - }
|
| - }
|
| -
|
| - linkToType(enclosingType, type);
|
| -
|
| - write(' ');
|
| - if (paramName !== null) write(paramName);
|
| - }
|
| -
|
| - /** Writes a link to a human-friendly string representation for a type. */
|
| - linkToType(Type enclosingType, Type type) {
|
| - if (type is ParameterType) {
|
| - // If we're using a type parameter within the body of a generic class then
|
| - // just link back up to the class.
|
| - write(a(typeUrl(enclosingType), type.name));
|
| - return;
|
| - }
|
| -
|
| - // Link to the type.
|
| - // Use .genericType to avoid writing the <...> here.
|
| - write(a(typeUrl(type), type.genericType.name));
|
| -
|
| - // See if it's a generic type.
|
| - if (type.isGeneric) {
|
| - // TODO(rnystrom): This relies on a weird corner case of frog. Currently,
|
| - // the only time we get into this case is when we have a "raw" generic
|
| - // that's been instantiated with Dynamic for all type arguments. It's kind
|
| - // of strange that frog works that way, but we take advantage of it to
|
| - // show raw types without any type arguments.
|
| - return;
|
| - }
|
| -
|
| - // See if it's an instantiation of a generic type.
|
| - final typeArgs = type.typeArgsInOrder;
|
| - if (typeArgs.length > 0) {
|
| - write('<');
|
| - bool first = true;
|
| - for (final arg in typeArgs) {
|
| - if (!first) write(', ');
|
| - first = false;
|
| - linkToType(enclosingType, arg);
|
| - }
|
| - write('>');
|
| - }
|
| - }
|
| -
|
| - /** Creates a linked cross reference to [type]. */
|
| - typeReference(Type type) {
|
| - // TODO(rnystrom): Do we need to handle ParameterTypes here like
|
| - // annotation() does?
|
| - return a(typeUrl(type), typeName(type), css: 'crossref');
|
| - }
|
| -
|
| - /** Generates a human-friendly string representation for a type. */
|
| - typeName(Type type, [bool showBounds = false]) {
|
| - // See if it's a generic type.
|
| - if (type.isGeneric) {
|
| - final typeParams = [];
|
| - for (final typeParam in type.genericType.typeParameters) {
|
| - if (showBounds &&
|
| - (typeParam.extendsType != null) &&
|
| - !typeParam.extendsType.isObject) {
|
| - final bound = typeName(typeParam.extendsType, showBounds: true);
|
| - typeParams.add('${typeParam.name} extends $bound');
|
| - } else {
|
| - typeParams.add(typeParam.name);
|
| - }
|
| - }
|
| -
|
| - final params = Strings.join(typeParams, ', ');
|
| - return '${type.name}<$params>';
|
| - }
|
| -
|
| - // See if it's an instantiation of a generic type.
|
| - final typeArgs = type.typeArgsInOrder;
|
| - if (typeArgs.length > 0) {
|
| - final args = Strings.join(map(typeArgs, (arg) => typeName(arg)), ', ');
|
| - return '${type.genericType.name}<$args>';
|
| - }
|
| -
|
| - // Regular type.
|
| - return type.name;
|
| - }
|
| -
|
| - /**
|
| - * Remove leading indentation to line up with first line.
|
| - */
|
| - unindentCode(SourceSpan span) {
|
| - final column = getSpanColumn(span);
|
| - final lines = span.text.split('\n');
|
| - // TODO(rnystrom): Dirty hack.
|
| - for (var i = 1; i < lines.length; i++) {
|
| - lines[i] = unindent(lines[i], column);
|
| - }
|
| -
|
| - final code = Strings.join(lines, '\n');
|
| - return code;
|
| - }
|
| -
|
| - /**
|
| - * Takes a string of Dart code and turns it into sanitized HTML.
|
| - */
|
| - formatCode(SourceSpan span) {
|
| - final code = unindentCode(span);
|
| -
|
| - // Syntax highlight.
|
| - return classifySource(new SourceFile('', code));
|
| - }
|
| -
|
| - /**
|
| - * This will be called whenever a doc comment hits a `[name]` in square
|
| - * brackets. It will try to figure out what the name refers to and link or
|
| - * style it appropriately.
|
| - */
|
| - md.Node resolveNameReference(String name, [Member member = null,
|
| - Type type = null, Library library = null]) {
|
| - makeLink(String href) {
|
| - final anchor = new md.Element.text('a', name);
|
| - anchor.attributes['href'] = relativePath(href);
|
| - anchor.attributes['class'] = 'crossref';
|
| - return anchor;
|
| - }
|
| -
|
| - findMember(Type type, String memberName) {
|
| - final member = type.members[memberName];
|
| - if (member == null) return null;
|
| -
|
| - // Special case: if the member we've resolved is a property (i.e. it wraps
|
| - // a getter and/or setter then *that* member itself won't be on the docs,
|
| - // just the getter or setter will be. So pick one of those to link to.
|
| - if (member.isProperty) {
|
| - return member.canGet ? member.getter : member.setter;
|
| - }
|
| -
|
| - return member;
|
| - }
|
| -
|
| - // See if it's a parameter of the current method.
|
| - if (member != null) {
|
| - for (final parameter in member.parameters) {
|
| - if (parameter.name == name) {
|
| - final element = new md.Element.text('span', name);
|
| - element.attributes['class'] = 'param';
|
| - return element;
|
| - }
|
| - }
|
| - }
|
| -
|
| - // See if it's another member of the current type.
|
| - if (type != null) {
|
| - final member = findMember(type, name);
|
| - if (member != null) {
|
| - return makeLink(memberUrl(member));
|
| - }
|
| - }
|
| -
|
| - // See if it's another type or a member of another type in the current
|
| - // library.
|
| - if (library != null) {
|
| - // See if it's a constructor
|
| - final constructorLink = (() {
|
| - final match = new RegExp(@'new (\w+)(?:\.(\w+))?').firstMatch(name);
|
| - if (match == null) return;
|
| - final type = library.types[match[1]];
|
| - if (type == null) return;
|
| - final constructor = type.getConstructor(
|
| - match[2] == null ? '' : match[2]);
|
| - if (constructor == null) return;
|
| - return makeLink(memberUrl(constructor));
|
| - })();
|
| - if (constructorLink != null) return constructorLink;
|
| -
|
| - // See if it's a member of another type
|
| - final foreignMemberLink = (() {
|
| - final match = new RegExp(@'(\w+)\.(\w+)').firstMatch(name);
|
| - if (match == null) return;
|
| - final type = library.types[match[1]];
|
| - if (type == null) return;
|
| - final member = findMember(type, match[2]);
|
| - if (member == null) return;
|
| - return makeLink(memberUrl(member));
|
| - })();
|
| - if (foreignMemberLink != null) return foreignMemberLink;
|
| -
|
| - final type = library.types[name];
|
| - if (type != null) {
|
| - return makeLink(typeUrl(type));
|
| - }
|
| -
|
| - // See if it's a top-level member in the current library.
|
| - final member = findMember(library.topType, name);
|
| - if (member != null) {
|
| - return makeLink(memberUrl(member));
|
| - }
|
| - }
|
| -
|
| - // TODO(rnystrom): Should also consider:
|
| - // * Names imported by libraries this library imports.
|
| - // * Type parameters of the enclosing type.
|
| -
|
| - return new md.Element.text('code', name);
|
| - }
|
| -
|
| - // TODO(rnystrom): Move into SourceSpan?
|
| - int getSpanColumn(SourceSpan span) {
|
| - final line = span.file.getLine(span.start);
|
| - return span.file.getColumn(line, span.start);
|
| - }
|
| -}
|
|
|