Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(201)

Side by Side Diff: pkg/dartdoc/dartdoc.dart

Issue 10919260: Reorganize dartdoc to new package layout. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/dartdoc/comment_map.dart ('k') | pkg/dartdoc/dropdown.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 /**
6 * To generate docs for a library, run this script with the path to an
7 * entrypoint .dart file, like:
8 *
9 * $ dart dartdoc.dart foo.dart
10 *
11 * This will create a "docs" directory with the docs for your libraries. To
12 * create these beautiful docs, dartdoc parses your library and every library
13 * it imports (recursively). From each library, it parses all classes and
14 * members, finds the associated doc comments and builds crosslinked docs from
15 * them.
16 */
17 #library('dartdoc');
18
19 #import('dart:io');
20 #import('dart:math');
21 #import('dart:uri');
22 #import('dart:json');
23 #import('mirrors/mirrors.dart');
24 #import('mirrors/mirrors_util.dart');
25 #import('mirrors/dart2js_mirror.dart', prefix: 'dart2js');
26 #import('classify.dart');
27 #import('markdown.dart', prefix: 'md');
28 #import('../../lib/compiler/implementation/scanner/scannerlib.dart',
29 prefix: 'dart2js');
30 #import('../../lib/_internal/libraries.dart');
31
32 #source('comment_map.dart');
33 #source('nav.dart');
34 #source('utils.dart');
35
36 // TODO(johnniwinther): Note that [IN_SDK] gets initialized to true when this
37 // file is modified by the SDK deployment script. If you change, be sure to test
38 // that dartdoc still works when run from the built SDK directory.
39 const bool IN_SDK = false;
40
41 /**
42 * Generates completely static HTML containing everything you need to browse
43 * the docs. The only client side behavior is trivial stuff like syntax
44 * highlighting code.
45 */
46 const MODE_STATIC = 0;
47
48 /**
49 * Generated docs do not include baked HTML navigation. Instead, a single
50 * `nav.json` file is created and the appropriate navigation is generated
51 * client-side by parsing that and building HTML.
52 *
53 * This dramatically reduces the generated size of the HTML since a large
54 * fraction of each static page is just redundant navigation links.
55 *
56 * In this mode, the browser will do a XHR for nav.json which means that to
57 * preview docs locally, you will need to enable requesting file:// links in
58 * your browser or run a little local server like `python -m SimpleHTTPServer`.
59 */
60 const MODE_LIVE_NAV = 1;
61
62 const API_LOCATION = 'http://api.dartlang.org/';
63
64 /**
65 * Run this from the `pkg/dartdoc` directory.
66 */
67 void main() {
68 final args = new Options().arguments;
69
70 final dartdoc = new Dartdoc();
71
72 if (args.isEmpty()) {
73 print('No arguments provided.');
74 printUsage();
75 return;
76 }
77
78 final entrypoints = <Path>[];
79
80 var i = 0;
81 while (i < args.length) {
82 final arg = args[i];
83 if (!arg.startsWith('--')) {
84 // The remaining arguments must be entry points.
85 break;
86 }
87
88 switch (arg) {
89 case '--no-code':
90 dartdoc.includeSource = false;
91 break;
92
93 case '--mode=static':
94 dartdoc.mode = MODE_STATIC;
95 break;
96
97 case '--mode=live-nav':
98 dartdoc.mode = MODE_LIVE_NAV;
99 break;
100
101 case '--generate-app-cache':
102 case '--generate-app-cache=true':
103 dartdoc.generateAppCache = true;
104 break;
105
106 case '--omit-generation-time':
107 dartdoc.omitGenerationTime = true;
108 break;
109 case '--verbose':
110 dartdoc.verbose = true;
111 break;
112 case '--include-api':
113 dartdoc.includeApi = true;
114 break;
115 case '--link-api':
116 dartdoc.linkToApi = true;
117 break;
118
119 default:
120 if (arg.startsWith('--out=')) {
121 dartdoc.outputDir =
122 new Path.fromNative(arg.substring('--out='.length));
123 } else if (arg.startsWith('--include-lib=')) {
124 dartdoc.includedLibraries =
125 arg.substring('--include-lib='.length).split(',');
126 } else if (arg.startsWith('--exclude-lib=')) {
127 dartdoc.excludedLibraries =
128 arg.substring('--exclude-lib='.length).split(',');
129 } else {
130 print('Unknown option: $arg');
131 printUsage();
132 return;
133 }
134 break;
135 }
136 i++;
137 }
138 while (i < args.length) {
139 final arg = args[i];
140 entrypoints.add(new Path.fromNative(arg));
141 i++;
142 }
143
144 if (entrypoints.isEmpty()) {
145 print('No entrypoints provided.');
146 printUsage();
147 return;
148 }
149
150 cleanOutputDirectory(dartdoc.outputDir);
151
152 dartdoc.documentLibraries(entrypoints, libPath);
153
154 // Compile the client-side code to JS.
155 final clientScript = (dartdoc.mode == MODE_STATIC) ? 'static' : 'live-nav';
156 Future compiled = compileScript(
157 scriptDir.append('client-$clientScript.dart'),
158 dartdoc.outputDir.append('client-$clientScript.js'));
159
160 Future filesCopied = copyDirectory(scriptDir.append('static'),
161 dartdoc.outputDir);
162
163 Futures.wait([compiled, filesCopied]).then((_) {
164 dartdoc.cleanup();
165 print('Documented ${dartdoc._totalLibraries} libraries, '
166 '${dartdoc._totalTypes} types, and '
167 '${dartdoc._totalMembers} members.');
168 });
169 }
170
171 void printUsage() {
172 print('''
173 Usage dartdoc [options] <entrypoint(s)>
174 [options] include
175 --no-code Do not include source code in the documentation.
176
177 --mode=static Generates completely static HTML containing
178 everything you need to browse the docs. The only
179 client side behavior is trivial stuff like syntax
180 highlighting code.
181
182 --mode=live-nav (default) Generated docs do not include baked HTML
183 navigation. Instead, a single `nav.json` file is
184 created and the appropriate navigation is generated
185 client-side by parsing that and building HTML.
186 This dramatically reduces the generated size of
187 the HTML since a large fraction of each static page
188 is just redundant navigation links.
189 In this mode, the browser will do a XHR for
190 nav.json which means that to preview docs locally,
191 you will need to enable requesting file:// links in
192 your browser or run a little local server like
193 `python -m SimpleHTTPServer`.
194
195 --generate-app-cache Generates the App Cache manifest file, enabling
196 offline doc viewing.
197
198 --out=<dir> Generates files into directory <dir>. If omitted
199 the files are generated into ./docs/
200
201 --link-api Link to the online language API in the generated
202 documentation. The option overrides inclusion
203 through --include-api or --include-lib.
204
205 --include-api Include the used API libraries in the generated
206 documentation. If the --link-api option is used,
207 this option is ignored.
208
209 --include-lib=<libs> Use this option to explicitly specify which
210 libraries to include in the documentation. If
211 omitted, all used libraries are included by
212 default. <libs> is comma-separated list of library
213 names.
214
215 --exclude-lib=<libs> Use this option to explicitly specify which
216 libraries to exclude from the documentation. If
217 omitted, no libraries are excluded. <libs> is
218 comma-separated list of library names.
219
220 --verbose Print verbose information during generation.
221 ''');
222 }
223
224 /**
225 * Gets the full path to the directory containing the entrypoint of the current
226 * script. In other words, if you invoked dartdoc, directly, it will be the
227 * path to the directory containing `dartdoc.dart`. If you're running a script
228 * that imports dartdoc, it will be the path to that script.
229 */
230 // TODO(johnniwinther): Convert to final (lazily initialized) variables when
231 // the feature is supported.
232 Path get scriptDir =>
233 new Path.fromNative(new Options().script).directoryPath;
234
235 // TODO(johnniwinther): Trailing slashes matter due to the use of [libPath] as
236 // a base URI with [Uri.resolve].
237 /// Relative path to the library in which dart2js resides.
238 Path get libPath => IN_SDK
239 ? scriptDir.append('../../lib/dart2js/')
240 : scriptDir.append('../../');
241
242 /**
243 * Deletes and recreates the output directory at [path] if it exists.
244 */
245 void cleanOutputDirectory(Path path) {
246 final outputDir = new Directory.fromPath(path);
247 if (outputDir.existsSync()) {
248 outputDir.deleteRecursivelySync();
249 }
250
251 try {
252 // TODO(3914): Hack to avoid 'file already exists' exception thrown
253 // due to invalid result from dir.existsSync() (probably due to race
254 // conditions).
255 outputDir.createSync();
256 } on DirectoryIOException catch (e) {
257 // Ignore.
258 }
259 }
260
261 /**
262 * Copies all of the files in the directory [from] to [to]. Does *not*
263 * recursively copy subdirectories.
264 *
265 * Note: runs asynchronously, so you won't see any files copied until after the
266 * event loop has had a chance to pump (i.e. after `main()` has returned).
267 */
268 Future copyDirectory(Path from, Path to) {
269 final completer = new Completer();
270 final fromDir = new Directory.fromPath(from);
271 final lister = fromDir.list(recursive: false);
272
273 lister.onFile = (String path) {
274 final name = new Path.fromNative(path).filename;
275 // TODO(rnystrom): Hackish. Ignore 'hidden' files like .DS_Store.
276 if (name.startsWith('.')) return;
277
278 File fromFile = new File(path);
279 File toFile = new File.fromPath(to.append(name));
280 fromFile.openInputStream().pipe(toFile.openOutputStream());
281 };
282 lister.onDone = (done) => completer.complete(true);
283 return completer.future;
284 }
285
286 /**
287 * Compiles the given Dart script to a JavaScript file at [jsPath] using the
288 * Dart2js compiler.
289 */
290 Future<bool> compileScript(Path dartPath, Path jsPath) {
291 var completer = new Completer<bool>();
292 var compilation = new Compilation(dartPath, libPath);
293 Future<String> result = compilation.compileToJavaScript();
294 result.then((jsCode) {
295 writeString(new File.fromPath(jsPath), jsCode);
296 completer.complete(true);
297 });
298 result.handleException((e) => completer.completeException(e));
299 return completer.future;
300 }
301
302 class Dartdoc {
303
304 /** Set to `false` to not include the source code in the generated docs. */
305 bool includeSource = true;
306
307 /**
308 * Dartdoc can generate docs in a few different ways based on how dynamic you
309 * want the client-side behavior to be. The value for this should be one of
310 * the `MODE_` constants.
311 */
312 int mode = MODE_LIVE_NAV;
313
314 /**
315 * Generates the App Cache manifest file, enabling offline doc viewing.
316 */
317 bool generateAppCache = false;
318
319 /** Path to the dartdoc directory. */
320 Path dartdocPath;
321
322 /** Path to generate HTML files into. */
323 Path outputDir = const Path('docs');
324
325 /**
326 * The title used for the overall generated output. Set this to change it.
327 */
328 String mainTitle = 'Dart Documentation';
329
330 /**
331 * The URL that the Dart logo links to. Defaults "index.html", the main
332 * page for the generated docs, but can be anything.
333 */
334 String mainUrl = 'index.html';
335
336 /**
337 * The Google Custom Search ID that should be used for the search box. If
338 * this is `null` then no search box will be shown.
339 */
340 String searchEngineId = null;
341
342 /* The URL that the embedded search results should be displayed on. */
343 String searchResultsUrl = 'results.html';
344
345 /** Set this to add footer text to each generated page. */
346 String footerText = null;
347
348 /** Set this to add content before the footer */
349 String preFooterText = '';
350
351 /** Set this to omit generation timestamp from output */
352 bool omitGenerationTime = false;
353
354 /** Set by Dartdoc user to print extra information during generation. */
355 bool verbose = false;
356
357 /** Set this to include API libraries in the documentation. */
358 bool includeApi = false;
359
360 /** Set this to generate links to the online API. */
361 bool linkToApi = false;
362
363 /** Set this to select the libraries to include in the documentation. */
364 List<String> includedLibraries = const <String>[];
365
366 /** Set this to select the libraries to exclude from the documentation. */
367 List<String> excludedLibraries = const <String>[];
368
369 /**
370 * This list contains the libraries sorted in by the library name.
371 */
372 List<LibraryMirror> _sortedLibraries;
373
374 CommentMap _comments;
375
376 /** The library that we're currently generating docs for. */
377 LibraryMirror _currentLibrary;
378
379 /** The type that we're currently generating docs for. */
380 InterfaceMirror _currentType;
381
382 /** The member that we're currently generating docs for. */
383 MemberMirror _currentMember;
384
385 /** The path to the file currently being written to, relative to [outdir]. */
386 Path _filePath;
387
388 /** The file currently being written to. */
389 StringBuffer _file;
390
391 int _totalLibraries = 0;
392 int _totalTypes = 0;
393 int _totalMembers = 0;
394
395 Dartdoc()
396 : _comments = new CommentMap(),
397 dartdocPath = scriptDir {
398 // Patch in support for [:...:]-style code to the markdown parser.
399 // TODO(rnystrom): Markdown already has syntax for this. Phase this out?
400 md.InlineParser.syntaxes.insertRange(0, 1,
401 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]'));
402
403 md.setImplicitLinkResolver((name) => resolveNameReference(name,
404 currentLibrary: _currentLibrary, currentType: _currentType,
405 currentMember: _currentMember));
406 }
407
408 /**
409 * Returns `true` if [library] is included in the generated documentation.
410 */
411 bool shouldIncludeLibrary(LibraryMirror library) {
412 if (shouldLinkToPublicApi(library)) {
413 return false;
414 }
415 var includeByDefault = true;
416 String libraryName = library.simpleName;
417 if (!includedLibraries.isEmpty()) {
418 includeByDefault = false;
419 if (includedLibraries.indexOf(libraryName) != -1) {
420 return true;
421 }
422 }
423 if (excludedLibraries.indexOf(libraryName) != -1) {
424 return false;
425 }
426 if (libraryName.startsWith('dart:')) {
427 String suffix = libraryName.substring('dart:'.length);
428 LibraryInfo info = LIBRARIES[suffix];
429 if (info != null) {
430 return info.documented && includeApi;
431 }
432 }
433 return includeByDefault;
434 }
435
436 /**
437 * Returns `true` if links to the public API should be generated for
438 * [library].
439 */
440 bool shouldLinkToPublicApi(LibraryMirror library) {
441 if (linkToApi) {
442 String libraryName = library.simpleName;
443 if (libraryName.startsWith('dart:')) {
444 String suffix = libraryName.substring('dart:'.length);
445 LibraryInfo info = LIBRARIES[suffix];
446 if (info != null) {
447 return info.documented;
448 }
449 }
450 }
451 return false;
452 }
453
454 String get footerContent{
455 var footerItems = [];
456 if (!omitGenerationTime) {
457 footerItems.add("This page was generated at ${new Date.now()}");
458 }
459 if (footerText != null) {
460 footerItems.add(footerText);
461 }
462 var content = '';
463 for (int i = 0; i < footerItems.length; i++) {
464 if (i > 0) {
465 content = content.concat('\n');
466 }
467 content = content.concat('<div>${footerItems[i]}</div>');
468 }
469 return content;
470 }
471
472 void documentEntryPoint(Path entrypoint, Path libPath) {
473 final compilation = new Compilation(entrypoint, libPath);
474 _document(compilation);
475 }
476
477 void documentLibraries(List<Path> libraryList, Path libPath) {
478 final compilation = new Compilation.library(libraryList, libPath);
479 _document(compilation);
480 }
481
482 void _document(Compilation compilation) {
483 // Sort the libraries by name (not key).
484 _sortedLibraries = new List<LibraryMirror>.from(
485 compilation.mirrors.libraries.getValues().filter(
486 shouldIncludeLibrary));
487 _sortedLibraries.sort((x, y) {
488 return x.simpleName.toUpperCase().compareTo(
489 y.simpleName.toUpperCase());
490 });
491
492 // Generate the docs.
493 if (mode == MODE_LIVE_NAV) {
494 docNavigationJson();
495 } else {
496 docNavigationDart();
497 }
498
499 docIndex();
500 for (final library in _sortedLibraries) {
501 docLibrary(library);
502 }
503
504 if (generateAppCache) {
505 generateAppCacheManifest();
506 }
507 }
508
509 void startFile(String path) {
510 _filePath = new Path(path);
511 _file = new StringBuffer();
512 }
513
514 void endFile() {
515 final outPath = outputDir.join(_filePath);
516 final dir = new Directory.fromPath(outPath.directoryPath);
517 if (!dir.existsSync()) {
518 // TODO(3914): Hack to avoid 'file already exists' exception
519 // thrown due to invalid result from dir.existsSync() (probably due to
520 // race conditions).
521 try {
522 dir.createSync();
523 } on DirectoryIOException catch (e) {
524 // Ignore.
525 }
526 }
527
528 writeString(new File.fromPath(outPath), _file.toString());
529 _filePath = null;
530 _file = null;
531 }
532
533 void write(String s) {
534 _file.add(s);
535 }
536
537 void writeln(String s) {
538 write(s);
539 write('\n');
540 }
541
542 /**
543 * Writes the page header with the given [title] and [breadcrumbs]. The
544 * breadcrumbs are an interleaved list of links and titles. If a link is null,
545 * then no link will be generated. For example, given:
546 *
547 * ['foo', 'foo.html', 'bar', null]
548 *
549 * It will output:
550 *
551 * <a href="foo.html">foo</a> &rsaquo; bar
552 */
553 void writeHeader(String title, List<String> breadcrumbs) {
554 final htmlAttributes = generateAppCache ?
555 'manifest="/appcache.manifest"' : '';
556
557 write(
558 '''
559 <!DOCTYPE html>
560 <html${htmlAttributes == '' ? '' : ' $htmlAttributes'}>
561 <head>
562 ''');
563 writeHeadContents(title);
564
565 // Add data attributes describing what the page documents.
566 var data = '';
567 if (_currentLibrary != null) {
568 data = '$data data-library='
569 '"${md.escapeHtml(_currentLibrary.simpleName)}"';
570 }
571
572 if (_currentType != null) {
573 data = '$data data-type="${md.escapeHtml(typeName(_currentType))}"';
574 }
575
576 write(
577 '''
578 </head>
579 <body$data>
580 <div class="page">
581 <div class="header">
582 ${a(mainUrl, '<div class="logo"></div>')}
583 ${a('index.html', mainTitle)}
584 ''');
585
586 // Write the breadcrumb trail.
587 for (int i = 0; i < breadcrumbs.length; i += 2) {
588 if (breadcrumbs[i + 1] == null) {
589 write(' &rsaquo; ${breadcrumbs[i]}');
590 } else {
591 write(' &rsaquo; ${a(breadcrumbs[i + 1], breadcrumbs[i])}');
592 }
593 }
594
595 if (searchEngineId != null) {
596 writeln(
597 '''
598 <form action="$searchResultsUrl" id="search-box">
599 <input type="hidden" name="cx" value="$searchEngineId">
600 <input type="hidden" name="ie" value="UTF-8">
601 <input type="hidden" name="hl" value="en">
602 <input type="search" name="q" id="q" autocomplete="off"
603 class="search-input" placeholder="Search API">
604 </form>
605 ''');
606 } else {
607 writeln(
608 '''
609 <div id="search-box">
610 <input type="search" name="q" id="q" autocomplete="off"
611 class="search-input" placeholder="Search API">
612 </div>
613 ''');
614 }
615
616 writeln(
617 '''
618 </div>
619 <div class="drop-down" id="drop-down"></div>
620 ''');
621
622 docNavigation();
623 writeln('<div class="content">');
624 }
625
626 String get clientScript {
627 switch (mode) {
628 case MODE_STATIC: return 'client-static';
629 case MODE_LIVE_NAV: return 'client-live-nav';
630 default: throw 'Unknown mode $mode.';
631 }
632 }
633
634 void writeHeadContents(String title) {
635 writeln(
636 '''
637 <meta charset="utf-8">
638 <title>$title / $mainTitle</title>
639 <link rel="stylesheet" type="text/css"
640 href="${relativePath('styles.css')}">
641 <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700 ,800" rel="stylesheet" type="text/css">
642 <link rel="shortcut icon" href="${relativePath('favicon.ico')}">
643 ''');
644 }
645
646 void writeFooter() {
647 writeln(
648 '''
649 </div>
650 <div class="clear"></div>
651 </div>
652 ${preFooterText}
653 <div class="footer">
654 $footerContent
655 </div>
656 <script async src="${relativePath('$clientScript.js')}"></script>
657 </body></html>
658 ''');
659 }
660
661 void docIndex() {
662 startFile('index.html');
663
664 writeHeader(mainTitle, []);
665
666 writeln('<h2>$mainTitle</h2>');
667 writeln('<h3>Libraries</h3>');
668
669 for (final library in _sortedLibraries) {
670 docIndexLibrary(library);
671 }
672
673 writeFooter();
674 endFile();
675 }
676
677 void docIndexLibrary(LibraryMirror library) {
678 writeln('<h4>${a(libraryUrl(library), library.simpleName)}</h4>');
679 }
680
681 /**
682 * Walks the libraries and creates a JSON object containing the data needed
683 * to generate navigation for them.
684 */
685 void docNavigationJson() {
686 startFile('nav.json');
687 writeln(JSON.stringify(createNavigationInfo()));
688 endFile();
689 }
690
691 void docNavigationDart() {
692 final dir = new Directory.fromPath(tmpPath);
693 if (!dir.existsSync()) {
694 // TODO(3914): Hack to avoid 'file already exists' exception
695 // thrown due to invalid result from dir.existsSync() (probably due to
696 // race conditions).
697 try {
698 dir.createSync();
699 } on DirectoryIOException catch (e) {
700 // Ignore.
701 }
702 }
703 String jsonString = JSON.stringify(createNavigationInfo());
704 String dartString = jsonString.replaceAll(@"$", @"\$");
705 final filePath = tmpPath.append('nav.dart');
706 writeString(new File.fromPath(filePath),
707 'get json => $dartString;');
708 }
709
710 Path get tmpPath => dartdocPath.append('tmp');
711
712 void cleanup() {
713 final dir = new Directory.fromPath(tmpPath);
714 if (dir.existsSync()) {
715 dir.deleteRecursivelySync();
716 }
717 }
718
719 List createNavigationInfo() {
720 final libraryList = [];
721 for (final library in _sortedLibraries) {
722 docLibraryNavigationJson(library, libraryList);
723 }
724 return libraryList;
725 }
726
727 void docLibraryNavigationJson(LibraryMirror library, List libraryList) {
728 var libraryInfo = {};
729 libraryInfo[NAME] = library.simpleName;
730 final List members = docMembersJson(library.declaredMembers);
731 if (!members.isEmpty()) {
732 libraryInfo[MEMBERS] = members;
733 }
734
735 final types = [];
736 for (InterfaceMirror type in orderByName(library.types.getValues())) {
737 if (type.isPrivate) continue;
738
739 var typeInfo = {};
740 typeInfo[NAME] = type.simpleName;
741 if (type.isClass) {
742 typeInfo[KIND] = CLASS;
743 } else if (type.isInterface) {
744 typeInfo[KIND] = INTERFACE;
745 } else {
746 assert(type.isTypedef);
747 typeInfo[KIND] = TYPEDEF;
748 }
749 final List typeMembers = docMembersJson(type.declaredMembers);
750 if (!typeMembers.isEmpty()) {
751 typeInfo[MEMBERS] = typeMembers;
752 }
753
754 if (!type.declaration.typeVariables.isEmpty()) {
755 final typeVariables = [];
756 for (final typeVariable in type.declaration.typeVariables) {
757 typeVariables.add(typeVariable.simpleName);
758 }
759 typeInfo[ARGS] = Strings.join(typeVariables, ', ');
760 }
761 types.add(typeInfo);
762 }
763 if (!types.isEmpty()) {
764 libraryInfo[TYPES] = types;
765 }
766
767 libraryList.add(libraryInfo);
768 }
769
770 List docMembersJson(Map<Object,MemberMirror> memberMap) {
771 final members = [];
772 for (MemberMirror member in orderByName(memberMap.getValues())) {
773 if (member.isPrivate) continue;
774
775 var memberInfo = {};
776 if (member.isField) {
777 memberInfo[NAME] = member.simpleName;
778 memberInfo[KIND] = FIELD;
779 } else {
780 MethodMirror method = member;
781 if (method.isConstructor) {
782 if (method.constructorName != '') {
783 memberInfo[NAME] = '${method.simpleName}.${method.constructorName}';
784 memberInfo[KIND] = CONSTRUCTOR;
785 } else {
786 memberInfo[NAME] = member.simpleName;
787 memberInfo[KIND] = CONSTRUCTOR;
788 }
789 } else if (method.isOperator) {
790 memberInfo[NAME] = '${method.simpleName} ${method.operatorName}';
791 memberInfo[KIND] = METHOD;
792 } else if (method.isSetter) {
793 memberInfo[NAME] = member.simpleName;
794 memberInfo[KIND] = SETTER;
795 } else if (method.isGetter) {
796 memberInfo[NAME] = member.simpleName;
797 memberInfo[KIND] = GETTER;
798 } else {
799 memberInfo[NAME] = member.simpleName;
800 memberInfo[KIND] = METHOD;
801 }
802 }
803 var anchor = memberAnchor(member);
804 if (anchor != memberInfo[NAME]) {
805 memberInfo[LINK_NAME] = anchor;
806 }
807 members.add(memberInfo);
808 }
809 return members;
810 }
811
812 void docNavigation() {
813 writeln(
814 '''
815 <div class="nav">
816 ''');
817
818 if (mode == MODE_STATIC) {
819 for (final library in _sortedLibraries) {
820 write('<h2><div class="icon-library"></div>');
821
822 if ((_currentLibrary == library) && (_currentType == null)) {
823 write('<strong>${library.simpleName}</strong>');
824 } else {
825 write('${a(libraryUrl(library), library.simpleName)}');
826 }
827 write('</h2>');
828
829 // Only expand classes in navigation for current library.
830 if (_currentLibrary == library) docLibraryNavigation(library);
831 }
832 }
833
834 writeln('</div>');
835 }
836
837 /** Writes the navigation for the types contained by the given library. */
838 void docLibraryNavigation(LibraryMirror library) {
839 // Show the exception types separately.
840 final types = <InterfaceMirror>[];
841 final exceptions = <InterfaceMirror>[];
842
843 for (InterfaceMirror type in orderByName(library.types.getValues())) {
844 if (type.isPrivate) continue;
845
846 if (isException(type)) {
847 exceptions.add(type);
848 } else {
849 types.add(type);
850 }
851 }
852
853 if ((types.length == 0) && (exceptions.length == 0)) return;
854
855 writeln('<ul class="icon">');
856 types.forEach(docTypeNavigation);
857 exceptions.forEach(docTypeNavigation);
858 writeln('</ul>');
859 }
860
861 /** Writes a linked navigation list item for the given type. */
862 void docTypeNavigation(InterfaceMirror type) {
863 var icon = 'interface';
864 if (type.simpleName.endsWith('Exception')) {
865 icon = 'exception';
866 } else if (type.isClass) {
867 icon = 'class';
868 }
869
870 write('<li>');
871 if (_currentType == type) {
872 write(
873 '<div class="icon-$icon"></div><strong>${typeName(type)}</strong>');
874 } else {
875 write(a(typeUrl(type),
876 '<div class="icon-$icon"></div>${typeName(type)}'));
877 }
878 writeln('</li>');
879 }
880
881 void docLibrary(LibraryMirror library) {
882 if (verbose) {
883 print('Library \'${library.simpleName}\':');
884 }
885 _totalLibraries++;
886 _currentLibrary = library;
887 _currentType = null;
888
889 startFile(libraryUrl(library));
890 writeHeader('${library.simpleName} Library',
891 [library.simpleName, libraryUrl(library)]);
892 writeln('<h2><strong>${library.simpleName}</strong> library</h2>');
893
894 // Look for a comment for the entire library.
895 final comment = getLibraryComment(library);
896 if (comment != null) {
897 writeln('<div class="doc">$comment</div>');
898 }
899
900 // Document the top-level members.
901 docMembers(library);
902
903 // Document the types.
904 final classes = <InterfaceMirror>[];
905 final interfaces = <InterfaceMirror>[];
906 final typedefs = <TypedefMirror>[];
907 final exceptions = <InterfaceMirror>[];
908
909 for (InterfaceMirror type in orderByName(library.types.getValues())) {
910 if (type.isPrivate) continue;
911
912 if (isException(type)) {
913 exceptions.add(type);
914 } else if (type.isClass) {
915 classes.add(type);
916 } else if (type.isInterface){
917 interfaces.add(type);
918 } else if (type is TypedefMirror) {
919 typedefs.add(type);
920 } else {
921 throw new InternalError("internal error: unknown type $type.");
922 }
923 }
924
925 docTypes(classes, 'Classes');
926 docTypes(interfaces, 'Interfaces');
927 docTypes(typedefs, 'Typedefs');
928 docTypes(exceptions, 'Exceptions');
929
930 writeFooter();
931 endFile();
932
933 for (final type in library.types.getValues()) {
934 if (type.isPrivate) continue;
935
936 docType(type);
937 }
938 }
939
940 void docTypes(List types, String header) {
941 if (types.length == 0) return;
942
943 writeln('<h3>$header</h3>');
944
945 for (final type in types) {
946 writeln(
947 '''
948 <div class="type">
949 <h4>
950 ${a(typeUrl(type), "<strong>${typeName(type)}</strong>")}
951 </h4>
952 </div>
953 ''');
954 }
955 }
956
957 void docType(InterfaceMirror type) {
958 if (verbose) {
959 print('- ${type.simpleName}');
960 }
961 _totalTypes++;
962 _currentType = type;
963
964 startFile(typeUrl(type));
965
966 var kind = 'Interface';
967 if (type.isTypedef) {
968 kind = 'Typedef';
969 } else if (type.isClass) {
970 kind = 'Class';
971 }
972
973 final typeTitle =
974 '${typeName(type)} ${kind}';
975 writeHeader('$typeTitle / ${type.library.simpleName} Library',
976 [type.library.simpleName, libraryUrl(type.library),
977 typeName(type), typeUrl(type)]);
978 writeln(
979 '''
980 <h2><strong>${typeName(type, showBounds: true)}</strong>
981 $kind
982 </h2>
983 ''');
984
985 docCode(type.location, getTypeComment(type));
986 docInheritance(type);
987 docTypedef(type);
988 docConstructors(type);
989 docMembers(type);
990
991 writeTypeFooter();
992 writeFooter();
993 endFile();
994 }
995
996 /** Override this to write additional content at the end of a type's page. */
997 void writeTypeFooter() {
998 // Do nothing.
999 }
1000
1001 /**
1002 * Writes an inline type span for the given type. This is a little box with
1003 * an icon and the type's name. It's similar to how types appear in the
1004 * navigation, but is suitable for inline (as opposed to in a `<ul>`) use.
1005 */
1006 void typeSpan(InterfaceMirror type) {
1007 var icon = 'interface';
1008 if (type.simpleName.endsWith('Exception')) {
1009 icon = 'exception';
1010 } else if (type.isClass) {
1011 icon = 'class';
1012 }
1013
1014 write('<span class="type-box"><span class="icon-$icon"></span>');
1015 if (_currentType == type) {
1016 write('<strong>${typeName(type)}</strong>');
1017 } else {
1018 write(a(typeUrl(type), typeName(type)));
1019 }
1020 write('</span>');
1021 }
1022
1023 /**
1024 * Document the other types that touch [Type] in the inheritance hierarchy:
1025 * subclasses, superclasses, subinterfaces, superinferfaces, and default
1026 * class.
1027 */
1028 void docInheritance(InterfaceMirror type) {
1029 // Don't show the inheritance details for Object. It doesn't have any base
1030 // class (obviously) and it has too many subclasses to be useful.
1031 if (type.isObject) return;
1032
1033 // Writes an unordered list of references to types with an optional header.
1034 listTypes(types, header) {
1035 if (types == null) return;
1036
1037 // Skip private types.
1038 final publicTypes = new List.from(types.filter((t) => !t.isPrivate));
1039 if (publicTypes.length == 0) return;
1040
1041 writeln('<h3>$header</h3>');
1042 writeln('<p>');
1043 bool first = true;
1044 for (final t in publicTypes) {
1045 if (!first) write(', ');
1046 typeSpan(t);
1047 first = false;
1048 }
1049 writeln('</p>');
1050 }
1051
1052 final subtypes = [];
1053 for (final subtype in computeSubdeclarations(type)) {
1054 subtypes.add(subtype);
1055 }
1056 subtypes.sort((x, y) => x.simpleName.compareTo(y.simpleName));
1057 if (type.isClass) {
1058 // Show the chain of superclasses.
1059 if (!type.superclass.isObject) {
1060 final supertypes = [];
1061 var thisType = type.superclass;
1062 // As a sanity check, only show up to five levels of nesting, otherwise
1063 // the box starts to get hideous.
1064 do {
1065 supertypes.add(thisType);
1066 thisType = thisType.superclass;
1067 } while (!thisType.isObject);
1068
1069 writeln('<h3>Extends</h3>');
1070 writeln('<p>');
1071 for (var i = supertypes.length - 1; i >= 0; i--) {
1072 typeSpan(supertypes[i]);
1073 write('&nbsp;&gt;&nbsp;');
1074 }
1075
1076 // Write this class.
1077 typeSpan(type);
1078 writeln('</p>');
1079 }
1080
1081 listTypes(subtypes, 'Subclasses');
1082 listTypes(type.interfaces.getValues(), 'Implements');
1083 } else {
1084 // Show the default class.
1085 if (type.defaultType != null) {
1086 listTypes([type.defaultType], 'Default class');
1087 }
1088
1089 // List extended interfaces.
1090 listTypes(type.interfaces.getValues(), 'Extends');
1091
1092 // List subinterfaces and implementing classes.
1093 final subinterfaces = [];
1094 final implementing = [];
1095
1096 for (final subtype in subtypes) {
1097 if (subtype.isClass) {
1098 implementing.add(subtype);
1099 } else {
1100 subinterfaces.add(subtype);
1101 }
1102 }
1103
1104 listTypes(subinterfaces, 'Subinterfaces');
1105 listTypes(implementing, 'Implemented by');
1106 }
1107 }
1108
1109 /**
1110 * Documents the definition of [type] if it is a typedef.
1111 */
1112 void docTypedef(TypeMirror type) {
1113 if (type is! TypedefMirror) {
1114 return;
1115 }
1116 writeln('<div class="method"><h4 id="${type.simpleName}">');
1117
1118 if (includeSource) {
1119 writeln('<span class="show-code">Code</span>');
1120 }
1121
1122 write('typedef ');
1123 annotateType(type, type.definition, type.simpleName);
1124
1125 write(''' <a class="anchor-link" href="#${type.simpleName}"
1126 title="Permalink to ${type.simpleName}">#</a>''');
1127 writeln('</h4>');
1128
1129 docCode(type.location, null, showCode: true);
1130
1131 writeln('</div>');
1132 }
1133
1134 /** Document the constructors for [Type], if any. */
1135 void docConstructors(InterfaceMirror type) {
1136 final constructors = <MethodMirror>[];
1137 for (var constructor in type.constructors.getValues()) {
1138 if (!constructor.isPrivate) {
1139 constructors.add(constructor);
1140 }
1141 }
1142
1143 if (constructors.length > 0) {
1144 writeln('<h3>Constructors</h3>');
1145 constructors.sort((x, y) => x.simpleName.toUpperCase().compareTo(
1146 y.simpleName.toUpperCase()));
1147
1148 for (final constructor in constructors) {
1149 docMethod(type, constructor);
1150 }
1151 }
1152 }
1153
1154 void docMembers(ObjectMirror host) {
1155 // Collect the different kinds of members.
1156 final staticMethods = [];
1157 final staticFields = [];
1158 final instanceMethods = [];
1159 final instanceFields = [];
1160
1161 for (MemberMirror member in orderByName(host.declaredMembers.getValues())) {
1162 if (member.isPrivate) continue;
1163
1164 final methods = member.isStatic ? staticMethods : instanceMethods;
1165 final fields = member.isStatic ? staticFields : instanceFields;
1166
1167 if (member.isMethod) {
1168 methods.add(member);
1169 } else if (member.isField) {
1170 fields.add(member);
1171 }
1172 }
1173
1174 if (staticMethods.length > 0) {
1175 final title = host is LibraryMirror ? 'Functions' : 'Static Methods';
1176 writeln('<h3>$title</h3>');
1177 for (final method in orderByName(staticMethods)) {
1178 docMethod(host, method);
1179 }
1180 }
1181
1182 if (staticFields.length > 0) {
1183 final title = host is LibraryMirror ? 'Variables' : 'Static Fields';
1184 writeln('<h3>$title</h3>');
1185 for (final field in orderByName(staticFields)) {
1186 docField(host, field);
1187 }
1188 }
1189
1190 if (instanceMethods.length > 0) {
1191 writeln('<h3>Methods</h3>');
1192 for (final method in orderByName(instanceMethods)) {
1193 docMethod(host, method);
1194 }
1195 }
1196
1197 if (instanceFields.length > 0) {
1198 writeln('<h3>Fields</h3>');
1199 for (final field in orderByName(instanceFields)) {
1200 docField(host, field);
1201 }
1202 }
1203 }
1204
1205 /**
1206 * Documents the [method] in type [type]. Handles all kinds of methods
1207 * including getters, setters, and constructors.
1208 */
1209 void docMethod(ObjectMirror host, MethodMirror method) {
1210 _totalMembers++;
1211 _currentMember = method;
1212
1213 writeln('<div class="method"><h4 id="${memberAnchor(method)}">');
1214
1215 if (includeSource) {
1216 writeln('<span class="show-code">Code</span>');
1217 }
1218
1219 if (method.isConstructor) {
1220 if (method.isFactory) {
1221 write('factory ');
1222 } else {
1223 write(method.isConst ? 'const ' : 'new ');
1224 }
1225 }
1226
1227 if (method.constructorName == null) {
1228 annotateType(host, method.returnType);
1229 }
1230
1231 var name = method.simpleName;
1232 // Translate specially-named methods: getters, setters, operators.
1233 if (method.isGetter) {
1234 // Getter.
1235 name = 'get $name';
1236 } else if (method.isSetter) {
1237 // Setter.
1238 name = 'set $name';
1239 } else if (method.isOperator) {
1240 name = 'operator ${method.operatorName}';
1241 }
1242
1243 write('<strong>$name</strong>');
1244
1245 // Named constructors.
1246 if (method.constructorName != null && method.constructorName != '') {
1247 write('.');
1248 write(method.constructorName);
1249 }
1250
1251 docParamList(host, method.parameters);
1252
1253 var prefix = host is LibraryMirror ? '' : '${typeName(host)}.';
1254 write(''' <a class="anchor-link" href="#${memberAnchor(method)}"
1255 title="Permalink to $prefix$name">#</a>''');
1256 writeln('</h4>');
1257
1258 docCode(method.location, getMethodComment(method), showCode: true);
1259
1260 writeln('</div>');
1261 }
1262
1263 /** Documents the field [field] of type [type]. */
1264 void docField(ObjectMirror host, FieldMirror field) {
1265 _totalMembers++;
1266 _currentMember = field;
1267
1268 writeln('<div class="field"><h4 id="${memberAnchor(field)}">');
1269
1270 if (includeSource) {
1271 writeln('<span class="show-code">Code</span>');
1272 }
1273
1274 if (field.isFinal) {
1275 write('final ');
1276 } else if (field.type.isDynamic) {
1277 write('var ');
1278 }
1279
1280 annotateType(host, field.type);
1281 var prefix = host is LibraryMirror ? '' : '${typeName(host)}.';
1282 write(
1283 '''
1284 <strong>${field.simpleName}</strong> <a class="anchor-link"
1285 href="#${memberAnchor(field)}"
1286 title="Permalink to $prefix${field.simpleName}">#</a>
1287 </h4>
1288 ''');
1289
1290 docCode(field.location, getFieldComment(field), showCode: true);
1291 writeln('</div>');
1292 }
1293
1294 void docParamList(ObjectMirror enclosingType,
1295 List<ParameterMirror> parameters) {
1296 write('(');
1297 bool first = true;
1298 bool inOptionals = false;
1299 for (final parameter in parameters) {
1300 if (!first) write(', ');
1301
1302 if (!inOptionals && parameter.isOptional) {
1303 write('[');
1304 inOptionals = true;
1305 }
1306
1307 annotateType(enclosingType, parameter.type, parameter.simpleName);
1308
1309 // Show the default value for named optional parameters.
1310 if (parameter.isOptional && parameter.hasDefaultValue) {
1311 write(' = ');
1312 write(parameter.defaultValue);
1313 }
1314
1315 first = false;
1316 }
1317
1318 if (inOptionals) write(']');
1319 write(')');
1320 }
1321
1322 /**
1323 * Documents the code contained within [span] with [comment]. If [showCode]
1324 * is `true` (and [includeSource] is set), also includes the source code.
1325 */
1326 void docCode(Location location, String comment, [bool showCode = false]) {
1327 writeln('<div class="doc">');
1328 if (comment != null) {
1329 writeln(comment);
1330 }
1331
1332 if (includeSource && showCode) {
1333 writeln('<pre class="source">');
1334 writeln(md.escapeHtml(unindentCode(location)));
1335 writeln('</pre>');
1336 }
1337
1338 writeln('</div>');
1339 }
1340
1341
1342 /** Get the doc comment associated with the given library. */
1343 String getLibraryComment(LibraryMirror library) {
1344 // Look for a comment for the entire library.
1345 final comment = _comments.findLibrary(library.location.source);
1346 if (comment != null) {
1347 return md.markdownToHtml(comment);
1348 }
1349 return null;
1350 }
1351
1352 /** Get the doc comment associated with the given type. */
1353 String getTypeComment(TypeMirror type) {
1354 String comment = _comments.find(type.location);
1355 if (comment == null) return null;
1356 return commentToHtml(comment);
1357 }
1358
1359 /** Get the doc comment associated with the given method. */
1360 String getMethodComment(MethodMirror method) {
1361 String comment = _comments.find(method.location);
1362 if (comment == null) return null;
1363 return commentToHtml(comment);
1364 }
1365
1366 /** Get the doc comment associated with the given field. */
1367 String getFieldComment(FieldMirror field) {
1368 String comment = _comments.find(field.location);
1369 if (comment == null) return null;
1370 return commentToHtml(comment);
1371 }
1372
1373 String commentToHtml(String comment) => md.markdownToHtml(comment);
1374
1375 /**
1376 * Converts [fullPath] which is understood to be a full path from the root of
1377 * the generated docs to one relative to the current file.
1378 */
1379 String relativePath(String fullPath) {
1380 // Don't make it relative if it's an absolute path.
1381 if (isAbsolute(fullPath)) return fullPath;
1382
1383 // TODO(rnystrom): Walks all the way up to root each time. Shouldn't do
1384 // this if the paths overlap.
1385 return '${repeat('../',
1386 countOccurrences(_filePath.toString(), '/'))}$fullPath';
1387 }
1388
1389 /** Gets whether or not the given URL is absolute or relative. */
1390 bool isAbsolute(String url) {
1391 // TODO(rnystrom): Why don't we have a nice type in the platform for this?
1392 // TODO(rnystrom): This is a bit hackish. We consider any URL that lacks
1393 // a scheme to be relative.
1394 return const RegExp(@'^\w+:').hasMatch(url);
1395 }
1396
1397 /** Gets the URL to the documentation for [library]. */
1398 String libraryUrl(LibraryMirror library) {
1399 return '${sanitize(library.simpleName)}.html';
1400 }
1401
1402 /** Gets the URL for the documentation for [type]. */
1403 String typeUrl(ObjectMirror type) {
1404 if (type is LibraryMirror) {
1405 return '${sanitize(type.simpleName)}.html';
1406 }
1407 assert (type is TypeMirror);
1408 // Always get the generic type to strip off any type parameters or
1409 // arguments. If the type isn't generic, genericType returns `this`, so it
1410 // works for non-generic types too.
1411 return '${sanitize(type.library.simpleName)}/'
1412 '${type.declaration.simpleName}.html';
1413 }
1414
1415 /** Gets the URL for the documentation for [member]. */
1416 String memberUrl(MemberMirror member) {
1417 String url = typeUrl(member.surroundingDeclaration);
1418 return '$url#${memberAnchor(member)}';
1419 }
1420
1421 /** Gets the anchor id for the document for [member]. */
1422 String memberAnchor(MemberMirror member) {
1423 if (member.isField) {
1424 return member.simpleName;
1425 }
1426 MethodMirror method = member;
1427 if (method.isConstructor) {
1428 if (method.constructorName == '') {
1429 return method.simpleName;
1430 } else {
1431 return '${method.simpleName}.${method.constructorName}';
1432 }
1433 } else if (method.isOperator) {
1434 return '${method.simpleName} ${method.operatorName}';
1435 } else if (method.isSetter) {
1436 return '${method.simpleName}=';
1437 } else {
1438 return method.simpleName;
1439 }
1440 }
1441
1442 /**
1443 * Creates a hyperlink. Handles turning the [href] into an appropriate
1444 * relative path from the current file.
1445 */
1446 String a(String href, String contents, [String css]) {
1447 // Mark outgoing external links, mainly so we can style them.
1448 final rel = isAbsolute(href) ? ' ref="external"' : '';
1449 final cssClass = css == null ? '' : ' class="$css"';
1450 return '<a href="${relativePath(href)}"$cssClass$rel>$contents</a>';
1451 }
1452
1453 /**
1454 * Writes a type annotation for the given type and (optional) parameter name.
1455 */
1456 annotateType(ObjectMirror enclosingType,
1457 TypeMirror type,
1458 [String paramName = null]) {
1459 // Don't bother explicitly displaying Dynamic.
1460 if (type.isDynamic) {
1461 if (paramName !== null) write(paramName);
1462 return;
1463 }
1464
1465 // For parameters, handle non-typedefed function types.
1466 if (paramName !== null && type is FunctionTypeMirror) {
1467 annotateType(enclosingType, type.returnType);
1468 write(paramName);
1469
1470 docParamList(enclosingType, type.parameters);
1471 return;
1472 }
1473
1474 linkToType(enclosingType, type);
1475
1476 write(' ');
1477 if (paramName !== null) write(paramName);
1478 }
1479
1480 /** Writes a link to a human-friendly string representation for a type. */
1481 linkToType(ObjectMirror enclosingType, TypeMirror type) {
1482 if (type.isVoid) {
1483 // Do not generate links for void.
1484 // TODO(johnniwinter): Generate span for specific style?
1485 write('void');
1486 return;
1487 }
1488 if (type.isDynamic) {
1489 // Do not generate links for Dynamic.
1490 write('Dynamic');
1491 return;
1492 }
1493
1494 if (type.isTypeVariable) {
1495 // If we're using a type parameter within the body of a generic class then
1496 // just link back up to the class.
1497 write(a(typeUrl(enclosingType), type.simpleName));
1498 return;
1499 }
1500
1501 assert(type is InterfaceMirror);
1502
1503 // Link to the type.
1504 if (shouldLinkToPublicApi(type.library)) {
1505 write('<a href="$API_LOCATION${typeUrl(type)}">${type.simpleName}</a>');
1506 } else if (shouldIncludeLibrary(type.library)) {
1507 write(a(typeUrl(type), type.simpleName));
1508 } else {
1509 write(type.simpleName);
1510 }
1511
1512 if (type.isDeclaration) {
1513 // Avoid calling [:typeArguments():] on a declaration.
1514 return;
1515 }
1516
1517 // See if it's an instantiation of a generic type.
1518 final typeArgs = type.typeArguments;
1519 if (typeArgs.length > 0) {
1520 write('&lt;');
1521 bool first = true;
1522 for (final arg in typeArgs) {
1523 if (!first) write(', ');
1524 first = false;
1525 linkToType(enclosingType, arg);
1526 }
1527 write('&gt;');
1528 }
1529 }
1530
1531 /** Creates a linked cross reference to [type]. */
1532 typeReference(InterfaceMirror type) {
1533 // TODO(rnystrom): Do we need to handle ParameterTypes here like
1534 // annotation() does?
1535 return a(typeUrl(type), typeName(type), css: 'crossref');
1536 }
1537
1538 /** Generates a human-friendly string representation for a type. */
1539 typeName(TypeMirror type, [bool showBounds = false]) {
1540 if (type.isVoid) {
1541 return 'void';
1542 }
1543 if (type is TypeVariableMirror) {
1544 return type.simpleName;
1545 }
1546 assert(type is InterfaceMirror);
1547
1548 // See if it's a generic type.
1549 if (type.isDeclaration) {
1550 final typeParams = [];
1551 for (final typeParam in type.declaration.typeVariables) {
1552 if (showBounds &&
1553 (typeParam.bound != null) &&
1554 !typeParam.bound.isObject) {
1555 final bound = typeName(typeParam.bound, showBounds: true);
1556 typeParams.add('${typeParam.simpleName} extends $bound');
1557 } else {
1558 typeParams.add(typeParam.simpleName);
1559 }
1560 }
1561 if (typeParams.isEmpty()) {
1562 return type.simpleName;
1563 }
1564 final params = Strings.join(typeParams, ', ');
1565 return '${type.simpleName}&lt;$params&gt;';
1566 }
1567
1568 // See if it's an instantiation of a generic type.
1569 final typeArgs = type.typeArguments;
1570 if (typeArgs.length > 0) {
1571 final args = Strings.join(typeArgs.map((arg) => typeName(arg)), ', ');
1572 return '${type.declaration.simpleName}&lt;$args&gt;';
1573 }
1574
1575 // Regular type.
1576 return type.simpleName;
1577 }
1578
1579 /**
1580 * Remove leading indentation to line up with first line.
1581 */
1582 unindentCode(Location span) {
1583 final column = getLocationColumn(span);
1584 final lines = span.text.split('\n');
1585 // TODO(rnystrom): Dirty hack.
1586 for (var i = 1; i < lines.length; i++) {
1587 lines[i] = unindent(lines[i], column);
1588 }
1589
1590 final code = Strings.join(lines, '\n');
1591 return code;
1592 }
1593
1594 /**
1595 * Takes a string of Dart code and turns it into sanitized HTML.
1596 */
1597 formatCode(Location span) {
1598 final code = unindentCode(span);
1599
1600 // Syntax highlight.
1601 return classifySource(code);
1602 }
1603
1604 /**
1605 * This will be called whenever a doc comment hits a `[name]` in square
1606 * brackets. It will try to figure out what the name refers to and link or
1607 * style it appropriately.
1608 */
1609 md.Node resolveNameReference(String name,
1610 [MemberMirror currentMember = null,
1611 ObjectMirror currentType = null,
1612 LibraryMirror currentLibrary = null]) {
1613 makeLink(String href) {
1614 final anchor = new md.Element.text('a', name);
1615 anchor.attributes['href'] = relativePath(href);
1616 anchor.attributes['class'] = 'crossref';
1617 return anchor;
1618 }
1619
1620 // See if it's a parameter of the current method.
1621 if (currentMember is MethodMirror) {
1622 for (final parameter in currentMember.parameters) {
1623 if (parameter.simpleName == name) {
1624 final element = new md.Element.text('span', name);
1625 element.attributes['class'] = 'param';
1626 return element;
1627 }
1628 }
1629 }
1630
1631 // See if it's another member of the current type.
1632 if (currentType != null) {
1633 final foundMember = findMirror(currentType.declaredMembers, name);
1634 if (foundMember != null) {
1635 return makeLink(memberUrl(foundMember));
1636 }
1637 }
1638
1639 // See if it's another type or a member of another type in the current
1640 // library.
1641 if (currentLibrary != null) {
1642 // See if it's a constructor
1643 final constructorLink = (() {
1644 final match =
1645 new RegExp(@'new ([\w$]+)(?:\.([\w$]+))?').firstMatch(name);
1646 if (match == null) return;
1647 InterfaceMirror foundtype = findMirror(currentLibrary.types, match[1]);
1648 if (foundtype == null) return;
1649 final constructor =
1650 findMirror(foundtype.constructors,
1651 match[2] == null ? '' : match[2]);
1652 if (constructor == null) return;
1653 return makeLink(memberUrl(constructor));
1654 })();
1655 if (constructorLink != null) return constructorLink;
1656
1657 // See if it's a member of another type
1658 final foreignMemberLink = (() {
1659 final match = new RegExp(@'([\w$]+)\.([\w$]+)').firstMatch(name);
1660 if (match == null) return;
1661 InterfaceMirror foundtype = findMirror(currentLibrary.types, match[1]);
1662 if (foundtype == null) return;
1663 MemberMirror foundMember = findMirror(foundtype.declaredMembers, match[2 ]);
1664 if (foundMember == null) return;
1665 return makeLink(memberUrl(foundMember));
1666 })();
1667 if (foreignMemberLink != null) return foreignMemberLink;
1668
1669 InterfaceMirror foundType = findMirror(currentLibrary.types, name);
1670 if (foundType != null) {
1671 return makeLink(typeUrl(foundType));
1672 }
1673
1674 // See if it's a top-level member in the current library.
1675 MemberMirror foundMember = findMirror(currentLibrary.declaredMembers, name );
1676 if (foundMember != null) {
1677 return makeLink(memberUrl(foundMember));
1678 }
1679 }
1680
1681 // TODO(rnystrom): Should also consider:
1682 // * Names imported by libraries this library imports.
1683 // * Type parameters of the enclosing type.
1684
1685 return new md.Element.text('code', name);
1686 }
1687
1688 generateAppCacheManifest() {
1689 if (verbose) {
1690 print('Generating app cache manifest from output $outputDir');
1691 }
1692 startFile('appcache.manifest');
1693 write("CACHE MANIFEST\n\n");
1694 write("# VERSION: ${new Date.now()}\n\n");
1695 write("NETWORK:\n*\n\n");
1696 write("CACHE:\n");
1697 var toCache = new Directory.fromPath(outputDir);
1698 var toCacheLister = toCache.list(recursive: true);
1699 toCacheLister.onFile = (filename) {
1700 if (filename.endsWith('appcache.manifest')) {
1701 return;
1702 }
1703 // TODO(johnniwinther): If [outputDir] has trailing slashes, [filename]
1704 // contains double (back)slashes for files in the immediate [toCache]
1705 // directory. These are not handled by [relativeTo] thus
1706 // wrongfully producing the path `/foo.html` for a file `foo.html` in
1707 // [toCache].
1708 //
1709 // This can be handled in two ways. 1) By ensuring that
1710 // [Directory.fromPath] does not receive a path with a trailing slash, or
1711 // better, by making [Directory.fromPath] handle such trailing slashes.
1712 // 2) By ensuring that [filePath] does not have double slashes before
1713 // calling [relativeTo], or better, by making [relativeTo] handle double
1714 // slashes correctly.
1715 Path filePath = new Path.fromNative(filename).canonicalize();
1716 Path relativeFilePath = filePath.relativeTo(outputDir);
1717 write("$relativeFilePath\n");
1718 };
1719 toCacheLister.onDone = (done) => endFile();
1720 }
1721
1722 /**
1723 * Returns [:true:] if [type] should be regarded as an exception.
1724 */
1725 bool isException(TypeMirror type) {
1726 return type.simpleName.endsWith('Exception');
1727 }
1728 }
1729
1730 /**
1731 * Used to report an unexpected error in the DartDoc tool or the
1732 * underlying data
1733 */
1734 class InternalError {
1735 final String message;
1736 const InternalError(this.message);
1737 String toString() => "InternalError: '$message'";
1738 }
OLDNEW
« no previous file with comments | « pkg/dartdoc/comment_map.dart ('k') | pkg/dartdoc/dropdown.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698