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

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

Issue 10701091: Dartdoc and Apidoc updated to use dart2js through the mirror system. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Fixed cf. comments Created 8 years, 5 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
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 /** 5 /**
6 * To generate docs for a library, run this script with the path to an 6 * To generate docs for a library, run this script with the path to an
7 * entrypoint .dart file, like: 7 * entrypoint .dart file, like:
8 * 8 *
9 * $ dart dartdoc.dart foo.dart 9 * $ dart dartdoc.dart foo.dart
10 * 10 *
11 * This will create a "docs" directory with the docs for your libraries. To 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 12 * create these beautiful docs, dartdoc parses your library and every library
13 * it imports (recursively). From each library, it parses all classes and 13 * it imports (recursively). From each library, it parses all classes and
14 * members, finds the associated doc comments and builds crosslinked docs from 14 * members, finds the associated doc comments and builds crosslinked docs from
15 * them. 15 * them.
16 */ 16 */
17 #library('dartdoc'); 17 #library('dartdoc');
18 18
19 #import('dart:io'); 19 #import('dart:io');
20 #import('dart:uri');
20 #import('dart:json'); 21 #import('dart:json');
21 #import('frog/lang.dart'); 22 #import('../compiler/implementation/util/characters.dart');
22 #import('frog/file_system.dart'); 23 #import('mirrors/mirrors.dart');
23 #import('frog/file_system_vm.dart'); 24 #import('mirrors/mirrors_util.dart');
25 #import('mirrors/dart2js_mirror.dart', prefix: 'dart2js');
24 #import('classify.dart'); 26 #import('classify.dart');
25 #import('markdown.dart', prefix: 'md'); 27 #import('markdown.dart', prefix: 'md');
28 #import('../compiler/implementation/dart2js.dart', prefix: 'dart2js');
29 #import('../compiler/implementation/scanner/scannerlib.dart',
30 prefix: 'dart2js');
31 #import('file_util.dart');
26 32
27 #source('comment_map.dart'); 33 #source('comment_map.dart');
28 #source('utils.dart'); 34 #source('utils.dart');
29 35
30 /** 36 /**
31 * Generates completely static HTML containing everything you need to browse 37 * Generates completely static HTML containing everything you need to browse
32 * the docs. The only client side behavior is trivial stuff like syntax 38 * the docs. The only client side behavior is trivial stuff like syntax
33 * highlighting code. 39 * highlighting code.
34 */ 40 */
35 final MODE_STATIC = 0; 41 final MODE_STATIC = 0;
(...skipping 17 matching lines...) Expand all
53 */ 59 */
54 void main() { 60 void main() {
55 final args = new Options().arguments; 61 final args = new Options().arguments;
56 62
57 // Parse the dartdoc options. 63 // Parse the dartdoc options.
58 bool includeSource; 64 bool includeSource;
59 int mode; 65 int mode;
60 String outputDir; 66 String outputDir;
61 bool generateAppCache; 67 bool generateAppCache;
62 bool omitGenerationTime; 68 bool omitGenerationTime;
69 bool verbose;
70
71 if (args.isEmpty()) {
72 print('No arguments provided.');
73 printUsage();
74 return;
75 }
63 76
64 for (int i = 0; i < args.length - 1; i++) { 77 for (int i = 0; i < args.length - 1; i++) {
65 final arg = args[i]; 78 final arg = args[i];
66 79
67 switch (arg) { 80 switch (arg) {
68 case '--no-code': 81 case '--no-code':
69 includeSource = false; 82 includeSource = false;
70 break; 83 break;
71 84
72 case '--mode=static': 85 case '--mode=static':
73 mode = MODE_STATIC; 86 mode = MODE_STATIC;
74 break; 87 break;
75 88
76 case '--mode=live-nav': 89 case '--mode=live-nav':
77 mode = MODE_LIVE_NAV; 90 mode = MODE_LIVE_NAV;
78 break; 91 break;
79 92
80 case '--generate-app-cache': 93 case '--generate-app-cache':
81 case '--generate-app-cache=true': 94 case '--generate-app-cache=true':
82 generateAppCache = true; 95 generateAppCache = true;
83 break; 96 break;
84 97
85 case '--omit-generation-time': 98 case '--omit-generation-time':
86 omitGenerationTime = true; 99 omitGenerationTime = true;
87 break; 100 break;
101 case '--verbose':
102 verbose = true;
103 break;
88 104
89 default: 105 default:
90 if (arg.startsWith('--out=')) { 106 if (arg.startsWith('--out=')) {
91 outputDir = arg.substring('--out='.length); 107 outputDir = arg.substring('--out='.length);
92 } else { 108 } else {
93 print('Unknown option: $arg'); 109 print('Unknown option: $arg');
110 printUsage();
94 return; 111 return;
95 } 112 }
96 break; 113 break;
97 } 114 }
98 } 115 }
99 116
100 if (args.length == 0) { 117 if (args.length == 0) {
101 print('Provide at least one dart file to process.'); 118 print('Provide at least one dart file to process.');
102 return; 119 return;
103 } 120 }
104 121
105 // The entrypoint of the library to generate docs for.
106 final entrypoint = args[args.length - 1];
107
108 final files = new VMFileSystem();
109
110 // TODO(rnystrom): Note that the following lines get munged by create-sdk to 122 // TODO(rnystrom): Note that the following lines get munged by create-sdk to
111 // work with the SDK's different file layout. If you change, be sure to test 123 // work with the SDK's different file layout. If you change, be sure to test
112 // that dartdoc still works when run from the built SDK directory. 124 // that dartdoc still works when run from the built SDK directory.
113 final frogPath = joinPaths(scriptDir, 'frog/'); 125 final String libPath = joinPaths(scriptDir, '../');
114 final libDir = joinPaths(scriptDir, '..');
115 final compilerPath
116 = Platform.operatingSystem == 'windows' ? 'dart2js.bat' : 'dart2js';
117 126
118 parseOptions(frogPath, ['', '', '--libdir=$libDir'], files); 127 // The entrypoint of the library to generate docs for.
119 initializeWorld(files); 128 // TODO(johnniwinther): Handle absolute/relative paths
129 final entrypoint = canonicalizePath(args[args.length - 1]);
120 130
121 final dartdoc = new Dartdoc(); 131 final dartdoc = new Dartdoc();
122 132
123 if (includeSource != null) dartdoc.includeSource = includeSource; 133 if (includeSource != null) dartdoc.includeSource = includeSource;
124 if (mode != null) dartdoc.mode = mode; 134 if (mode != null) dartdoc.mode = mode;
125 if (outputDir != null) dartdoc.outputDir = outputDir; 135 if (outputDir != null) dartdoc.outputDir = outputDir;
126 if (generateAppCache != null) dartdoc.generateAppCache = generateAppCache; 136 if (generateAppCache != null) dartdoc.generateAppCache = generateAppCache;
127 if (omitGenerationTime != null) { 137 if (omitGenerationTime != null) {
128 dartdoc.omitGenerationTime = omitGenerationTime; 138 dartdoc.omitGenerationTime = omitGenerationTime;
129 } 139 }
140 if (verbose != null) dartdoc.verbose = verbose;
130 141
131 cleanOutputDirectory(dartdoc.outputDir); 142 cleanOutputDirectory(dartdoc.outputDir);
132 143
144 dartdoc.documentEntryPoint(entrypoint, libPath);
145
133 // Compile the client-side code to JS. 146 // Compile the client-side code to JS.
134 final clientScript = (dartdoc.mode == MODE_STATIC) ? 'static' : 'live-nav'; 147 final clientScript = (dartdoc.mode == MODE_STATIC) ? 'static' : 'live-nav';
135 final Future scriptCompiled = compileScript(compilerPath, 148 final bool scriptCompiled = compileScript(
136 '$scriptDir/client-$clientScript.dart', 149 '$scriptDir/client-$clientScript.dart',
137 '${dartdoc.outputDir}/client-$clientScript.js'); 150 '${dartdoc.outputDir}/client-$clientScript.js');
138 151
139 final Future filesCopied = copyFiles('$scriptDir/static', dartdoc.outputDir); 152 if (scriptCompiled) {
153 final Future filesCopied = copyFiles('$scriptDir/static',
154 dartdoc.outputDir);
140 155
141 Futures.wait([scriptCompiled, filesCopied]).then((_) { 156 Futures.wait([filesCopied]).then((_) {
142 dartdoc.document(entrypoint); 157 print('Documented ${dartdoc._totalLibraries} libraries, '
158 '${dartdoc._totalTypes} types, and '
159 '${dartdoc._totalMembers} members.');
160 });
161 }
162 }
143 163
144 print('Documented ${dartdoc._totalLibraries} libraries, ' 164 void printUsage() {
Bob Nystrom 2012/07/09 16:59:55 This is very nice, but at some point (not in this
Johnni Winther 2012/07/12 08:51:39 Yes.
145 '${dartdoc._totalTypes} types, and ' 165 print('''
146 '${dartdoc._totalMembers} members.'); 166 Usage dartdoc [options] <entrypoint>
147 }); 167 [options] include
168 --no-code Do not include source code in the documentation.
169
170 --mode=static Generates completely static HTML containing
171 everything you need to browse the docs. The only
172 client side behavior is trivial stuff like syntax
173 highlighting code.
174
175 --mode=live-nav (default) Generated docs do not include baked HTML
176 navigation. Instead, a single `nav.json` file is
177 created and the appropriate navigation is generated
178 client-side by parsing that and building HTML.
179 This dramatically reduces the generated size of
180 the HTML since a large fraction of each static page
181 is just redundant navigation links.
182 In this mode, the browser will do a XHR for
183 nav.json which means that to preview docs locally,
184 you will need to enable requesting file:// links in
185 your browser or run a little local server like
186 `python -m SimpleHTTPServer`.
187
188 --generate-app-cache Generates the App Cache manifest file, enabling
189 offline doc viewing.
190
191 --out=<dir> Generates files into directory <dir>. If omitted
192 the files are generated into ./docs/
193
194 --verbose Print verbose information during generation.
195 ''');
148 } 196 }
149 197
150 /** 198 /**
151 * Gets the full path to the directory containing the entrypoint of the current 199 * Gets the full path to the directory containing the entrypoint of the current
152 * script. In other words, if you invoked dartdoc, directly, it will be the 200 * script. In other words, if you invoked dartdoc, directly, it will be the
153 * path to the directory containing `dartdoc.dart`. If you're running a script 201 * path to the directory containing `dartdoc.dart`. If you're running a script
154 * that imports dartdoc, it will be the path to that script. 202 * that imports dartdoc, it will be the path to that script.
155 */ 203 */
156 String get scriptDir() { 204 String get scriptDir() {
157 return dirname(new File(new Options().script).fullPathSync()); 205 return dirname(new File(new Options().script).fullPathSync());
158 } 206 }
159 207
160 /** 208 /**
161 * Deletes and recreates the output directory at [path] if it exists. 209 * Deletes and recreates the output directory at [path] if it exists.
162 */ 210 */
163 void cleanOutputDirectory(String path) { 211 void cleanOutputDirectory(String path) {
164 final outputDir = new Directory(path); 212 final outputDir = new Directory(path);
165 if (outputDir.existsSync()) { 213 if (outputDir.existsSync()) {
166 outputDir.deleteRecursivelySync(); 214 outputDir.deleteRecursivelySync();
167 } 215 }
168 216
169 outputDir.createSync(); 217 try {
218 // TODO(johnniwinther): Hack to avoid 'file already exists' exception thrown
219 // due to invalid result from dir.existsSync() (probably due to race
220 // conditions).
221 outputDir.createSync();
222 } catch (DirectoryIOException e) {
223 // Ignore.
224 }
170 } 225 }
171 226
172 /** 227 /**
173 * Copies all of the files in the directory [from] to [to]. Does *not* 228 * Copies all of the files in the directory [from] to [to]. Does *not*
174 * recursively copy subdirectories. 229 * recursively copy subdirectories.
175 * 230 *
176 * Note: runs asynchronously, so you won't see any files copied until after the 231 * Note: runs asynchronously, so you won't see any files copied until after the
177 * event loop has had a chance to pump (i.e. after `main()` has returned). 232 * event loop has had a chance to pump (i.e. after `main()` has returned).
178 */ 233 */
179 Future copyFiles(String from, String to) { 234 Future copyFiles(String from, String to) {
(...skipping 12 matching lines...) Expand all
192 stream.write(bytes, copyBuffer: false); 247 stream.write(bytes, copyBuffer: false);
193 stream.close(); 248 stream.close();
194 }); 249 });
195 }; 250 };
196 lister.onDone = (done) => completer.complete(true); 251 lister.onDone = (done) => completer.complete(true);
197 return completer.future; 252 return completer.future;
198 } 253 }
199 254
200 /** 255 /**
201 * Compiles the given Dart script to a JavaScript file at [jsPath] using the 256 * Compiles the given Dart script to a JavaScript file at [jsPath] using the
202 * Dart-to-JS compiler located at [compilerPath]. 257 * Dart2js compiler.
203 */ 258 */
204 Future compileScript(String compilerPath, String dartPath, String jsPath) { 259 bool compileScript(String dartPath, String jsPath) {
205 final completer = new Completer(); 260 dart2js.compile([
206 onExit(ProcessResult result) { 261 '--no-colors',
207 if (result.exitCode != 0) { 262 // TODO(johnniwinther): The following lines get munged by create-sdk to
208 final message = 'Non-zero exit code from $compilerPath'; 263 // work with the SDK's different file layout. If you change, be sure to
209 print('$message.'); 264 // test that dartdoc still works when run from the built SDK directory.
210 print(result.stdout); 265 '--library-root=${joinPaths(scriptDir,'../../')}',
211 print(result.stderr); 266 '--out=$jsPath',
212 throw message; 267 '--throw-on-error',
213 } 268 '--suppress-warnings',
214 completer.complete(true); 269 dartPath]);
215 } 270 return true;
Bob Nystrom 2012/07/09 16:59:55 Why have this return value if it's always true?
Johnni Winther 2012/07/12 08:51:39 Legacy code. Return type changed to void.
216
217 onError(error) {
218 final message = 'Error trying to execute $compilerPath. Error: $error';
219 print('$message.');
220 throw message;
221 }
222
223 print('Compiling $dartPath to $jsPath');
224 var processFuture = Process.run(compilerPath, ['--out=$jsPath', dartPath]);
225 processFuture.handleException(onError);
226 processFuture.then(onExit);
227
228 return completer.future;
229 } 271 }
230 272
231 class Dartdoc { 273 class Dartdoc {
232 274
233 /** Set to `false` to not include the source code in the generated docs. */ 275 /** Set to `false` to not include the source code in the generated docs. */
234 bool includeSource = true; 276 bool includeSource = true;
235 277
236 /** 278 /**
237 * Dartdoc can generate docs in a few different ways based on how dynamic you 279 * Dartdoc can generate docs in a few different ways based on how dynamic you
238 * want the client-side behavior to be. The value for this should be one of 280 * want the client-side behavior to be. The value for this should be one of
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
270 312
271 /** Set this to add footer text to each generated page. */ 313 /** Set this to add footer text to each generated page. */
272 String footerText = null; 314 String footerText = null;
273 315
274 /** Set this to add content before the footer */ 316 /** Set this to add content before the footer */
275 String preFooterText = ''; 317 String preFooterText = '';
276 318
277 /** Set this to omit generation timestamp from output */ 319 /** Set this to omit generation timestamp from output */
278 bool omitGenerationTime = false; 320 bool omitGenerationTime = false;
279 321
322 /** Set by Dartdoc user to print extra information during generation. */
323 bool verbose = false;
324
325 /** Set this to select the libraries to document */
326 List<String> libraries = null;
327
280 /** 328 /**
281 * From exposes the set of libraries in `world.libraries`. That maps library 329 * From exposes the set of libraries in `world.libraries`. That maps library
Bob Nystrom 2012/07/09 16:59:55 This comment probably needs updating now.
Johnni Winther 2012/07/12 08:51:39 Done.
282 * *keys* to [Library] objects. The keys are *not* exactly the same as their 330 * *keys* to [Library] objects. The keys are *not* exactly the same as their
283 * names. This means if we order by key, we won't actually have them sorted 331 * names. This means if we order by key, we won't actually have them sorted
284 * correctly. This list contains the libraries in correct order by their 332 * correctly. This list contains the libraries in correct order by their
285 * *name*. 333 * *name*.
286 */ 334 */
287 List<Library> _sortedLibraries; 335 List<LibraryMirror> _sortedLibraries;
288 336
289 CommentMap _comments; 337 CommentMap _comments;
290 338
291 /** The library that we're currently generating docs for. */ 339 /** The library that we're currently generating docs for. */
292 Library _currentLibrary; 340 LibraryMirror _currentLibrary;
293 341
294 /** The type that we're currently generating docs for. */ 342 /** The type that we're currently generating docs for. */
295 Type _currentType; 343 InterfaceMirror _currentType;
296 344
297 /** The member that we're currently generating docs for. */ 345 /** The member that we're currently generating docs for. */
298 Member _currentMember; 346 MemberMirror _currentMember;
299 347
300 /** The path to the file currently being written to, relative to [outdir]. */ 348 /** The path to the file currently being written to, relative to [outdir]. */
301 String _filePath; 349 String _filePath;
302 350
303 /** The file currently being written to. */ 351 /** The file currently being written to. */
304 StringBuffer _file; 352 StringBuffer _file;
305 353
306 int _totalLibraries = 0; 354 int _totalLibraries = 0;
307 int _totalTypes = 0; 355 int _totalTypes = 0;
308 int _totalMembers = 0; 356 int _totalMembers = 0;
309 357
310 Dartdoc() 358 Dartdoc()
311 : _comments = new CommentMap() { 359 : _comments = new CommentMap() {
312 // Patch in support for [:...:]-style code to the markdown parser. 360 // Patch in support for [:...:]-style code to the markdown parser.
313 // TODO(rnystrom): Markdown already has syntax for this. Phase this out? 361 // TODO(rnystrom): Markdown already has syntax for this. Phase this out?
314 md.InlineParser.syntaxes.insertRange(0, 1, 362 md.InlineParser.syntaxes.insertRange(0, 1,
315 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); 363 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]'));
316 364
317 md.setImplicitLinkResolver((name) => resolveNameReference(name, 365 md.setImplicitLinkResolver((name) => resolveNameReference(name,
318 library: _currentLibrary, type: _currentType, 366 library: _currentLibrary, type: _currentType,
319 member: _currentMember)); 367 member: _currentMember));
320 } 368 }
321 369
370 bool includeLibrary(LibraryMirror library) {
371 if (libraries != null) {
372 return libraries.indexOf(library.simpleName()) != -1;
373 }
374 return true;
375 }
376
322 String get footerContent(){ 377 String get footerContent(){
323 var footerItems = []; 378 var footerItems = [];
324 if(!omitGenerationTime) { 379 if(!omitGenerationTime) {
325 footerItems.add("This page generated at ${new Date.now()}"); 380 footerItems.add("This page was generated at ${new Date.now()}");
326 } 381 }
327 if(footerText != null) { 382 if(footerText != null) {
328 footerItems.add(footerText); 383 footerItems.add(footerText);
329 } 384 }
330 var content = ''; 385 var content = '';
331 for (int i = 0; i < footerItems.length; i++) { 386 for (int i = 0; i < footerItems.length; i++) {
332 if(i > 0){ 387 if(i > 0){
333 content = content.concat('\n'); 388 content = content.concat('\n');
334 } 389 }
335 content = content.concat('<div>${footerItems[i]}</div>'); 390 content = content.concat('<div>${footerItems[i]}</div>');
336 } 391 }
337 return content; 392 return content;
338 } 393 }
339 394
340 void document([String entrypoint]) { 395 void documentEntryPoint(String entrypoint, String libPath) {
341 var oldDietParse = options.dietParse; 396 final compilation = new Compilation(entrypoint, libPath);
342 try { 397 _document(compilation);
343 options.dietParse = true; 398 }
344 399
345 // If we have an entrypoint, process it. Otherwise, just use whatever 400 void documentLibraries(List<String> libraries, String libPath) {
346 // libraries have been previously loaded by the calling code. 401 final compilation = new Compilation.library(libraries, libPath);
347 if (entrypoint != null) { 402 _document(compilation);
348 world.processDartScript(entrypoint); 403 }
349 }
350 404
351 world.resolveAll(); 405 void _document(Compilation compilation) {
406 // Sort the libraries by name (not key).
407 _sortedLibraries = new List<LibraryMirror>.from(
408 compilation.mirrors().libraries().getValues().filter(includeLibrary));
409 _sortedLibraries.sort((x, y) {
410 return x.simpleName().toUpperCase().compareTo(
411 y.simpleName().toUpperCase());
412 });
352 413
353 // Sort the libraries by name (not key). 414 // Generate the docs.
354 _sortedLibraries = world.libraries.getValues(); 415 if (mode == MODE_LIVE_NAV) docNavigationJson();
355 _sortedLibraries.sort((a, b) {
356 return a.name.toUpperCase().compareTo(b.name.toUpperCase());
357 });
358 416
359 // Generate the docs. 417 docIndex();
360 if (mode == MODE_LIVE_NAV) docNavigationJson(); 418 for (final library in _sortedLibraries) {
419 docLibrary(library);
420 }
361 421
362 docIndex(); 422 if (generateAppCache) {
363 for (final library in _sortedLibraries) { 423 generateAppCacheManifest();
364 docLibrary(library);
365 }
366
367 if (generateAppCache) {
368 generateAppCacheManifest();
369 }
370 } finally {
371 options.dietParse = oldDietParse;
372 } 424 }
373 } 425 }
374 426
375 void startFile(String path) { 427 void startFile(String path) {
376 _filePath = path; 428 _filePath = path;
377 _file = new StringBuffer(); 429 _file = new StringBuffer();
378 } 430 }
379 431
380 void endFile() { 432 void endFile() {
381 final outPath = '$outputDir/$_filePath'; 433 final outPath = '$outputDir/$_filePath';
382 final dir = new Directory(dirname(outPath)); 434 final dir = new Directory(dirname(outPath));
383 if (!dir.existsSync()) { 435 if (!dir.existsSync()) {
384 dir.createSync(); 436 // TODO(johnniwinther): Hack to avoid 'file already exists' exception
437 // thrown due to invalid result from dir.existsSync() (probably due to
438 // race conditions).
439 try {
440 dir.createSync();
441 } catch (DirectoryIOException e) {
442 // Ignore.
443 }
385 } 444 }
386 445
387 world.files.writeString(outPath, _file.toString()); 446 writeString(new File(outPath), _file.toString());
388 _filePath = null; 447 _filePath = null;
389 _file = null; 448 _file = null;
390 } 449 }
391 450
392 void write(String s) { 451 void write(String s) {
393 _file.add(s); 452 _file.add(s);
394 } 453 }
395 454
396 void writeln(String s) { 455 void writeln(String s) {
397 write(s); 456 write(s);
(...skipping 19 matching lines...) Expand all
417 ''' 476 '''
418 <!DOCTYPE html> 477 <!DOCTYPE html>
419 <html${htmlAttributes == '' ? '' : ' $htmlAttributes'}> 478 <html${htmlAttributes == '' ? '' : ' $htmlAttributes'}>
420 <head> 479 <head>
421 '''); 480 ''');
422 writeHeadContents(title); 481 writeHeadContents(title);
423 482
424 // Add data attributes describing what the page documents. 483 // Add data attributes describing what the page documents.
425 var data = ''; 484 var data = '';
426 if (_currentLibrary != null) { 485 if (_currentLibrary != null) {
427 data = '$data data-library="${md.escapeHtml(_currentLibrary.name)}"'; 486 data = '$data data-library='
487 '"${md.escapeHtml(_currentLibrary.simpleName())}"';
428 } 488 }
429 489
430 if (_currentType != null) { 490 if (_currentType != null) {
431 data = '$data data-type="${md.escapeHtml(typeName(_currentType))}"'; 491 data = '$data data-type="${md.escapeHtml(typeName(_currentType))}"';
432 } 492 }
433 493
434 write( 494 write(
435 ''' 495 '''
436 </head> 496 </head>
437 <body$data> 497 <body$data>
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
513 writeln('<h3>Libraries</h3>'); 573 writeln('<h3>Libraries</h3>');
514 574
515 for (final library in _sortedLibraries) { 575 for (final library in _sortedLibraries) {
516 docIndexLibrary(library); 576 docIndexLibrary(library);
517 } 577 }
518 578
519 writeFooter(); 579 writeFooter();
520 endFile(); 580 endFile();
521 } 581 }
522 582
523 void docIndexLibrary(Library library) { 583 void docIndexLibrary(LibraryMirror library) {
524 writeln('<h4>${a(libraryUrl(library), library.name)}</h4>'); 584 writeln('<h4>${a(libraryUrl(library), library.simpleName())}</h4>');
525 } 585 }
526 586
527 /** 587 /**
528 * Walks the libraries and creates a JSON object containing the data needed 588 * Walks the libraries and creates a JSON object containing the data needed
529 * to generate navigation for them. 589 * to generate navigation for them.
530 */ 590 */
531 void docNavigationJson() { 591 void docNavigationJson() {
532 startFile('nav.json'); 592 startFile('nav.json');
533 593
534 final libraries = {}; 594 final libraryMap = {};
535 595
536 for (final library in _sortedLibraries) { 596 for (final library in _sortedLibraries) {
537 docLibraryNavigationJson(library, libraries); 597 docLibraryNavigationJson(library, libraryMap);
538 } 598 }
539 599
540 writeln(JSON.stringify(libraries)); 600 writeln(JSON.stringify(libraryMap));
541 endFile(); 601 endFile();
542 } 602 }
543 603
544 void docLibraryNavigationJson(Library library, Map libraries) { 604 void docLibraryNavigationJson(LibraryMirror library, Map libraryMap) {
545 final types = []; 605 final types = [];
546 606
547 for (final type in orderByName(library.types)) { 607 for (final type in orderByName(library.types().getValues())) {
548 if (type.isTop) continue; 608 if (type.isPrivate) continue;
549 if (type.name.startsWith('_')) continue;
550 609
551 final kind = type.isClass ? 'class' : 'interface'; 610 final kind = type.isClass ? 'class' : 'interface';
552 final url = typeUrl(type); 611 final url = typeUrl(type);
553 types.add({ 'name': typeName(type), 'kind': kind, 'url': url }); 612 types.add({ 'name': typeName(type), 'kind': kind, 'url': url });
554 } 613 }
555 614
556 libraries[library.name] = types; 615 libraryMap[library.simpleName()] = types;
557 } 616 }
558 617
559 void docNavigation() { 618 void docNavigation() {
560 writeln( 619 writeln(
561 ''' 620 '''
562 <div class="nav"> 621 <div class="nav">
563 '''); 622 ''');
564 623
565 if (mode == MODE_STATIC) { 624 if (mode == MODE_STATIC) {
566 for (final library in _sortedLibraries) { 625 for (final library in _sortedLibraries) {
567 write('<h2><div class="icon-library"></div>'); 626 write('<h2><div class="icon-library"></div>');
568 627
569 if ((_currentLibrary == library) && (_currentType == null)) { 628 if ((_currentLibrary == library) && (_currentType == null)) {
570 write('<strong>${library.name}</strong>'); 629 write('<strong>${library.simpleName()}</strong>');
571 } else { 630 } else {
572 write('${a(libraryUrl(library), library.name)}'); 631 write('${a(libraryUrl(library), library.simpleName())}');
573 } 632 }
574 write('</h2>'); 633 write('</h2>');
575 634
576 // Only expand classes in navigation for current library. 635 // Only expand classes in navigation for current library.
577 if (_currentLibrary == library) docLibraryNavigation(library); 636 if (_currentLibrary == library) docLibraryNavigation(library);
578 } 637 }
579 } 638 }
580 639
581 writeln('</div>'); 640 writeln('</div>');
582 } 641 }
583 642
584 /** Writes the navigation for the types contained by the given library. */ 643 /** Writes the navigation for the types contained by the given library. */
585 void docLibraryNavigation(Library library) { 644 void docLibraryNavigation(LibraryMirror library) {
586 // Show the exception types separately. 645 // Show the exception types separately.
587 final types = <Type>[]; 646 final types = <InterfaceMirror>[];
588 final exceptions = <Type>[]; 647 final exceptions = <InterfaceMirror>[];
589 648
590 for (final type in orderByName(library.types)) { 649 for (final type in orderByName(library.types().getValues())) {
591 if (type.isTop) continue; 650 if (type.isPrivate) continue;
592 if (type.name.startsWith('_')) continue;
593 651
594 if (type.name.endsWith('Exception')) { 652 if (isException(type)) {
595 exceptions.add(type); 653 exceptions.add(type);
596 } else { 654 } else {
597 types.add(type); 655 types.add(type);
598 } 656 }
599 } 657 }
600 658
601 if ((types.length == 0) && (exceptions.length == 0)) return; 659 if ((types.length == 0) && (exceptions.length == 0)) return;
602 660
603 writeln('<ul class="icon">'); 661 writeln('<ul class="icon">');
604 types.forEach(docTypeNavigation); 662 types.forEach(docTypeNavigation);
605 exceptions.forEach(docTypeNavigation); 663 exceptions.forEach(docTypeNavigation);
606 writeln('</ul>'); 664 writeln('</ul>');
607 } 665 }
608 666
609 /** Writes a linked navigation list item for the given type. */ 667 /** Writes a linked navigation list item for the given type. */
610 void docTypeNavigation(Type type) { 668 void docTypeNavigation(InterfaceMirror type) {
611 var icon = 'interface'; 669 var icon = 'interface';
612 if (type.name.endsWith('Exception')) { 670 if (type.simpleName().endsWith('Exception')) {
613 icon = 'exception'; 671 icon = 'exception';
614 } else if (type.isClass) { 672 } else if (type.isClass) {
615 icon = 'class'; 673 icon = 'class';
616 } 674 }
617 675
618 write('<li>'); 676 write('<li>');
619 if (_currentType == type) { 677 if (_currentType == type) {
620 write( 678 write(
621 '<div class="icon-$icon"></div><strong>${typeName(type)}</strong>'); 679 '<div class="icon-$icon"></div><strong>${typeName(type)}</strong>');
622 } else { 680 } else {
623 write(a(typeUrl(type), 681 write(a(typeUrl(type),
624 '<div class="icon-$icon"></div>${typeName(type)}')); 682 '<div class="icon-$icon"></div>${typeName(type)}'));
625 } 683 }
626 writeln('</li>'); 684 writeln('</li>');
627 } 685 }
628 686
629 void docLibrary(Library library) { 687 void docLibrary(LibraryMirror library) {
688 if (verbose) {
689 print('Library \'${library.simpleName()}\':');
690 }
630 _totalLibraries++; 691 _totalLibraries++;
631 _currentLibrary = library; 692 _currentLibrary = library;
632 _currentType = null; 693 _currentType = null;
633 694
634 startFile(libraryUrl(library)); 695 startFile(libraryUrl(library));
635 writeHeader('${library.name} Library', 696 writeHeader('${library.simpleName()} Library',
636 [library.name, libraryUrl(library)]); 697 [library.simpleName(), libraryUrl(library)]);
637 writeln('<h2><strong>${library.name}</strong> library</h2>'); 698 writeln('<h2><strong>${library.simpleName()}</strong> library</h2>');
638 699
639 // Look for a comment for the entire library. 700 // Look for a comment for the entire library.
640 final comment = getLibraryComment(library); 701 final comment = getLibraryComment(library);
641 if (comment != null) { 702 if (comment != null) {
642 writeln('<div class="doc">$comment</div>'); 703 writeln('<div class="doc">$comment</div>');
643 } 704 }
644 705
645 // Document the top-level members. 706 // Document the top-level members.
646 docMembers(library.topType); 707 docMembers(library);
647 708
648 // Document the types. 709 // Document the types.
649 final classes = <Type>[]; 710 final classes = <InterfaceMirror>[];
650 final interfaces = <Type>[]; 711 final interfaces = <InterfaceMirror>[];
651 final exceptions = <Type>[]; 712 final exceptions = <InterfaceMirror>[];
652 713
653 for (final type in orderByName(library.types)) { 714 for (final type in orderByName(library.types().getValues())) {
654 if (type.isTop) continue; 715 if (type.isPrivate) continue;
655 if (type.name.startsWith('_')) continue;
656 716
657 if (type.name.endsWith('Exception')) { 717 if (isException(type)) {
658 exceptions.add(type); 718 exceptions.add(type);
659 } else if (type.isClass) { 719 } else if (type.isClass) {
660 classes.add(type); 720 classes.add(type);
661 } else { 721 } else {
662 interfaces.add(type); 722 interfaces.add(type);
663 } 723 }
664 } 724 }
665 725
666 docTypes(classes, 'Classes'); 726 docTypes(classes, 'Classes');
667 docTypes(interfaces, 'Interfaces'); 727 docTypes(interfaces, 'Interfaces');
668 docTypes(exceptions, 'Exceptions'); 728 docTypes(exceptions, 'Exceptions');
669 729
670 writeFooter(); 730 writeFooter();
671 endFile(); 731 endFile();
672 732
673 for (final type in library.types.getValues()) { 733 for (final type in library.types().getValues()) {
674 if (type.isTop) continue; 734 //if (type.isTop) continue;
Bob Nystrom 2012/07/09 16:59:55 Just delete this if it's dead code.
Johnni Winther 2012/07/12 08:51:39 Done.
675 if (type.name.startsWith('_')) continue; 735 if (type.isPrivate) continue;
736
676 docType(type); 737 docType(type);
677 } 738 }
678 } 739 }
679 740
680 void docTypes(List<Type> types, String header) { 741 void docTypes(List<InterfaceMirror> types, String header) {
681 if (types.length == 0) return; 742 if (types.length == 0) return;
682 743
683 writeln('<h3>$header</h3>'); 744 writeln('<h3>$header</h3>');
684 745
685 for (final type in types) { 746 for (final type in types) {
686 writeln( 747 writeln(
687 ''' 748 '''
688 <div class="type"> 749 <div class="type">
689 <h4> 750 <h4>
690 ${a(typeUrl(type), "<strong>${typeName(type)}</strong>")} 751 ${a(typeUrl(type), "<strong>${typeName(type)}</strong>")}
691 </h4> 752 </h4>
692 </div> 753 </div>
693 '''); 754 ''');
694 } 755 }
695 } 756 }
696 757
697 void docType(Type type) { 758 void docType(InterfaceMirror type) {
759 if (verbose) {
760 print('- ${type.simpleName()}');
761 }
698 _totalTypes++; 762 _totalTypes++;
699 _currentType = type; 763 _currentType = type;
700 764
701 startFile(typeUrl(type)); 765 startFile(typeUrl(type));
702 766
767 var kind = 'Interface';
768 if (type.isTypedef) {
769 kind = 'Typedef';
770 } else if (type.isClass) {
771 kind = 'Class';
772 }
773
703 final typeTitle = 774 final typeTitle =
704 '${typeName(type)} ${type.isClass ? "Class" : "Interface"}'; 775 '${typeName(type)} ${kind}';
705 writeHeader('$typeTitle / ${type.library.name} Library', 776 writeHeader('$typeTitle / ${type.library().simpleName()} Library',
706 [type.library.name, libraryUrl(type.library), 777 [type.library().simpleName(), libraryUrl(type.library()),
707 typeName(type), typeUrl(type)]); 778 typeName(type), typeUrl(type)]);
708 writeln( 779 writeln(
709 ''' 780 '''
710 <h2><strong>${typeName(type, showBounds: true)}</strong> 781 <h2><strong>${typeName(type, showBounds: true)}</strong>
711 ${type.isClass ? "Class" : "Interface"} 782 $kind
712 </h2> 783 </h2>
713 '''); 784 ''');
714 785
715 docCode(type.span, getTypeComment(type)); 786 docCode(type.location(), getTypeComment(type));
716 docInheritance(type); 787 docInheritance(type);
788 docTypedef(type);
717 docConstructors(type); 789 docConstructors(type);
718 docMembers(type); 790 docMembers(type);
719 791
720 writeTypeFooter(); 792 writeTypeFooter();
721 writeFooter(); 793 writeFooter();
722 endFile(); 794 endFile();
723 } 795 }
724 796
725 /** Override this to write additional content at the end of a type's page. */ 797 /** Override this to write additional content at the end of a type's page. */
726 void writeTypeFooter() { 798 void writeTypeFooter() {
727 // Do nothing. 799 // Do nothing.
728 } 800 }
729 801
730 /** 802 /**
731 * Writes an inline type span for the given type. This is a little box with 803 * Writes an inline type span for the given type. This is a little box with
732 * an icon and the type's name. It's similar to how types appear in the 804 * an icon and the type's name. It's similar to how types appear in the
733 * navigation, but is suitable for inline (as opposed to in a `<ul>`) use. 805 * navigation, but is suitable for inline (as opposed to in a `<ul>`) use.
734 */ 806 */
735 void typeSpan(Type type) { 807 void typeSpan(InterfaceMirror type) {
736 var icon = 'interface'; 808 var icon = 'interface';
737 if (type.name.endsWith('Exception')) { 809 if (type.simpleName().endsWith('Exception')) {
738 icon = 'exception'; 810 icon = 'exception';
739 } else if (type.isClass) { 811 } else if (type.isClass) {
740 icon = 'class'; 812 icon = 'class';
741 } 813 }
742 814
743 write('<span class="type-box"><span class="icon-$icon"></span>'); 815 write('<span class="type-box"><span class="icon-$icon"></span>');
744 if (_currentType == type) { 816 if (_currentType == type) {
745 write('<strong>${typeName(type)}</strong>'); 817 write('<strong>${typeName(type)}</strong>');
746 } else { 818 } else {
747 write(a(typeUrl(type), typeName(type))); 819 write(a(typeUrl(type), typeName(type)));
748 } 820 }
749 write('</span>'); 821 write('</span>');
750 } 822 }
751 823
752 /** 824 /**
753 * Document the other types that touch [Type] in the inheritance hierarchy: 825 * Document the other types that touch [Type] in the inheritance hierarchy:
754 * subclasses, superclasses, subinterfaces, superinferfaces, and default 826 * subclasses, superclasses, subinterfaces, superinferfaces, and default
755 * class. 827 * class.
756 */ 828 */
757 void docInheritance(Type type) { 829 void docInheritance(InterfaceMirror type) {
758 // Don't show the inheritance details for Object. It doesn't have any base 830 // Don't show the inheritance details for Object. It doesn't have any base
759 // class (obviously) and it has too many subclasses to be useful. 831 // class (obviously) and it has too many subclasses to be useful.
760 if (type.isObject) return; 832 if (type.isObject) return;
761 833
762 // Writes an unordered list of references to types with an optional header. 834 // Writes an unordered list of references to types with an optional header.
763 listTypes(types, header) { 835 listTypes(types, header) {
764 if (types == null) return; 836 if (types == null) return;
765 837
766 // Skip private types. 838 // Skip private types.
767 final publicTypes = types.filter((type) => !type.name.startsWith('_')); 839 final publicTypes
840 = new List.from(types).filter((t) => !t.isPrivate);
Bob Nystrom 2012/07/09 16:59:55 Style nit. I would wrap this after filter(
Johnni Winther 2012/07/12 08:51:39 Done.
768 if (publicTypes.length == 0) return; 841 if (publicTypes.length == 0) return;
769 842
770 writeln('<h3>$header</h3>'); 843 writeln('<h3>$header</h3>');
771 writeln('<p>'); 844 writeln('<p>');
772 bool first = true; 845 bool first = true;
773 for (final type in publicTypes) { 846 for (final t in publicTypes) {
774 if (!first) write(', '); 847 if (!first) write(', ');
775 typeSpan(type); 848 typeSpan(t);
776 first = false; 849 first = false;
777 } 850 }
778 writeln('</p>'); 851 writeln('</p>');
779 } 852 }
780 853
854 final subtypes = [];
855 for (final subtype in computeSubdeclarations(type)) {
856 subtypes.add(subtype);
857 }
858 subtypes.sort((x, y) => x.simpleName().compareTo(y.simpleName()));
781 if (type.isClass) { 859 if (type.isClass) {
782 // Show the chain of superclasses. 860 // Show the chain of superclasses.
783 if (!type.parent.isObject) { 861 if (!type.superclass().isObject) {
784 final supertypes = []; 862 final supertypes = [];
785 var thisType = type.parent; 863 var thisType = type.superclass();
786 // As a sanity check, only show up to five levels of nesting, otherwise 864 // As a sanity check, only show up to five levels of nesting, otherwise
787 // the box starts to get hideous. 865 // the box starts to get hideous.
788 do { 866 do {
789 supertypes.add(thisType); 867 supertypes.add(thisType);
790 thisType = thisType.parent; 868 thisType = thisType.superclass();
791 } while (!thisType.isObject); 869 } while (!thisType.isObject);
792 870
793 writeln('<h3>Extends</h3>'); 871 writeln('<h3>Extends</h3>');
794 writeln('<p>'); 872 writeln('<p>');
795 for (var i = supertypes.length - 1; i >= 0; i--) { 873 for (var i = supertypes.length - 1; i >= 0; i--) {
796 typeSpan(supertypes[i]); 874 typeSpan(supertypes[i]);
797 write('&nbsp;&gt;&nbsp;'); 875 write('&nbsp;&gt;&nbsp;');
798 } 876 }
799 877
800 // Write this class. 878 // Write this class.
801 typeSpan(type); 879 typeSpan(type);
802 writeln('</p>'); 880 writeln('</p>');
803 } 881 }
804 882
805 // Find the immediate declared subclasses (Type.subtypes includes many
806 // transitive subtypes).
807 final subtypes = [];
808 for (final subtype in type.subtypes) {
809 if (subtype.parent == type) subtypes.add(subtype);
810 }
811 subtypes.sort((a, b) => a.name.compareTo(b.name));
812
813 listTypes(subtypes, 'Subclasses'); 883 listTypes(subtypes, 'Subclasses');
814 listTypes(type.interfaces, 'Implements'); 884 listTypes(type.interfaces().getValues(), 'Implements');
815 } else { 885 } else {
816 // Show the default class. 886 // Show the default class.
817 if (type.genericType.defaultType != null) { 887 if (type.defaultType() != null) {
818 listTypes([type.genericType.defaultType], 'Default class'); 888 listTypes([type.defaultType()], 'Default class');
819 } 889 }
820 890
821 // List extended interfaces. 891 // List extended interfaces.
822 listTypes(type.interfaces, 'Extends'); 892 listTypes(type.interfaces().getValues(), 'Extends');
823 893
824 // List subinterfaces and implementing classes. 894 // List subinterfaces and implementing classes.
825 final subinterfaces = []; 895 final subinterfaces = [];
826 final implementing = []; 896 final implementing = [];
827 897
828 for (final subtype in type.subtypes) { 898 for (final subtype in subtypes) {
829 // We only want explicitly declared subinterfaces, so check that this 899 if (subtype.isClass) {
830 // type is a superinterface. 900 implementing.add(subtype);
831 for (final supertype in subtype.interfaces) { 901 } else {
832 if (supertype == type) { 902 subinterfaces.add(subtype);
833 if (subtype.isClass) {
834 implementing.add(subtype);
835 } else {
836 subinterfaces.add(subtype);
837 }
838 break;
839 }
840 } 903 }
841 } 904 }
842 905
843 listTypes(subinterfaces, 'Subinterfaces'); 906 listTypes(subinterfaces, 'Subinterfaces');
844 listTypes(implementing, 'Implemented by'); 907 listTypes(implementing, 'Implemented by');
845 } 908 }
846 } 909 }
847 910
911 /**
912 * Documents the [method] in type [type]. Handles all kinds of methods
913 * including getters, setters, and constructors.
Bob Nystrom 2012/07/09 16:59:55 Fix this comment.
Johnni Winther 2012/07/12 08:51:39 Done.
914 */
915 void docTypedef(TypeMirror type) {
916 if (type is! TypedefMirror) {
917 return;
918 }
919 writeln('<div class="method"><h4 id="${type.simpleName()}">');
920
921 if (includeSource) {
922 writeln('<span class="show-code">Code</span>');
923 }
924
925 if (type.definition() !== null) {
926 // TODO(johnniwinther): Implement [:TypedefMirror.definition():].
927 write('typedef ');
928 annotateType(type, type.definition(), type.simpleName());
929
930 write(''' <a class="anchor-link" href="#${type.simpleName()}"
931 title="Permalink to ${type.simpleName()}">#</a>''');
932 }
933 writeln('</h4>');
934
935 docCode(type.location(), null, showCode: true);
936
937 writeln('</div>');
938 }
939
848 /** Document the constructors for [Type], if any. */ 940 /** Document the constructors for [Type], if any. */
849 void docConstructors(Type type) { 941 void docConstructors(InterfaceMirror type) {
850 final names = type.constructors.getKeys().filter( 942 final constructors = <MethodMirror>[];
851 (name) => !name.startsWith('_')); 943 for (var constructor in type.constructors().getValues()) {
944 if (!constructor.isPrivate) {
945 constructors.add(constructor);
946 }
947 }
852 948
853 if (names.length > 0) { 949 if (constructors.length > 0) {
854 writeln('<h3>Constructors</h3>'); 950 writeln('<h3>Constructors</h3>');
855 names.sort((x, y) => x.toUpperCase().compareTo(y.toUpperCase())); 951 constructors.sort((x, y) => x.simpleName().toUpperCase().compareTo(
952 y.simpleName().toUpperCase()));
856 953
857 for (final name in names) { 954 for (final constructor in constructors) {
858 docMethod(type, type.constructors[name], constructorName: name); 955 docMethod(type, constructor);
859 } 956 }
860 } 957 }
861 } 958 }
862 959
863 void docMembers(Type type) { 960 void docMembers(ObjectMirror host) {
864 // Collect the different kinds of members. 961 // Collect the different kinds of members.
865 final staticMethods = []; 962 final staticMethods = [];
866 final staticFields = []; 963 final staticFields = [];
867 final instanceMethods = []; 964 final instanceMethods = [];
868 final instanceFields = []; 965 final instanceFields = [];
869 966
870 for (final member in orderByName(type.members)) { 967 for (final member in orderByName(host.declaredMembers().getValues())) {
871 if (member.name.startsWith('_')) continue; 968 if (member.isPrivate) continue;
872 969
873 final methods = member.isStatic ? staticMethods : instanceMethods; 970 final methods = member.isStatic ? staticMethods : instanceMethods;
874 final fields = member.isStatic ? staticFields : instanceFields; 971 final fields = member.isStatic ? staticFields : instanceFields;
875 972
876 if (member.isProperty) { 973 if (member.isMethod) {
877 if (member.canGet) methods.add(member.getter);
878 if (member.canSet) methods.add(member.setter);
879 } else if (member.isMethod) {
880 methods.add(member); 974 methods.add(member);
881 } else if (member.isField) { 975 } else if (member.isField) {
882 fields.add(member); 976 fields.add(member);
883 } 977 }
884 } 978 }
885 979
886 if (staticMethods.length > 0) { 980 if (staticMethods.length > 0) {
887 final title = type.isTop ? 'Functions' : 'Static Methods'; 981 final title = host is LibraryMirror ? 'Functions' : 'Static Methods';
888 writeln('<h3>$title</h3>'); 982 writeln('<h3>$title</h3>');
889 for (final method in staticMethods) docMethod(type, method); 983 for (final method in orderByName(staticMethods)) {
984 docMethod(host, method);
985 }
890 } 986 }
891 987
892 if (staticFields.length > 0) { 988 if (staticFields.length > 0) {
893 final title = type.isTop ? 'Variables' : 'Static Fields'; 989 final title = host is LibraryMirror ? 'Variables' : 'Static Fields';
894 writeln('<h3>$title</h3>'); 990 writeln('<h3>$title</h3>');
895 for (final field in staticFields) docField(type, field); 991 for (final field in orderByName(staticFields)) {
992 docField(host, field);
993 }
896 } 994 }
897 995
898 if (instanceMethods.length > 0) { 996 if (instanceMethods.length > 0) {
899 writeln('<h3>Methods</h3>'); 997 writeln('<h3>Methods</h3>');
900 for (final method in instanceMethods) docMethod(type, method); 998 for (final method in orderByName(instanceMethods)) {
999 docMethod(host, method);
1000 }
901 } 1001 }
902 1002
903 if (instanceFields.length > 0) { 1003 if (instanceFields.length > 0) {
904 writeln('<h3>Fields</h3>'); 1004 writeln('<h3>Fields</h3>');
905 for (final field in instanceFields) docField(type, field); 1005 for (final field in orderByName(instanceFields)) {
1006 docField(host, field);
1007 }
906 } 1008 }
907 } 1009 }
908 1010
909 /** 1011 /**
910 * Documents the [method] in type [type]. Handles all kinds of methods 1012 * Documents the [method] in type [type]. Handles all kinds of methods
911 * including getters, setters, and constructors. 1013 * including getters, setters, and constructors.
912 */ 1014 */
913 void docMethod(Type type, MethodMember method, 1015 void docMethod(ObjectMirror host, MethodMirror method) {
914 [String constructorName = null]) {
915 _totalMembers++; 1016 _totalMembers++;
916 _currentMember = method; 1017 _currentMember = method;
917 1018
918 writeln('<div class="method"><h4 id="${memberAnchor(method)}">'); 1019 writeln('<div class="method"><h4 id="${memberAnchor(method)}">');
919 1020
920 if (includeSource) { 1021 if (includeSource) {
921 writeln('<span class="show-code">Code</span>'); 1022 writeln('<span class="show-code">Code</span>');
922 } 1023 }
923 1024
924 if (method.isConstructor) { 1025 if (method.isConstructor) {
925 write(method.isConst ? 'const ' : 'new '); 1026 if (method.isFactory) {
1027 write('factory ');
1028 } else {
1029 write(method.isConst ? 'const ' : 'new ');
1030 }
926 } 1031 }
927 1032
928 if (constructorName == null) { 1033 if (method.constructorName == null) {
929 annotateType(type, method.returnType); 1034 annotateType(host, method.returnType());
930 } 1035 }
931 1036
1037 var name = method.simpleName();
932 // Translate specially-named methods: getters, setters, operators. 1038 // Translate specially-named methods: getters, setters, operators.
933 var name = method.name; 1039 if (method.isGetter) {
934 if (name.startsWith('get:')) {
935 // Getter. 1040 // Getter.
936 name = 'get ${name.substring(4)}'; 1041 name = 'get $name';
937 } else if (name.startsWith('set:')) { 1042 } else if (method.isSetter) {
938 // Setter. 1043 // Setter.
939 name = 'set ${name.substring(4)}'; 1044 name = 'set $name';
940 } else if (name == ':negate') { 1045 } else if (method.isOperator) {
941 // Dart uses 'negate' for prefix negate operators, not '!'. 1046 name = 'operator ${method.operatorName}';
942 name = 'operator negate';
943 } else if (name == ':call') {
944 name = 'operator call';
945 } else {
946 // See if it's an operator.
947 name = TokenKind.rawOperatorFromMethod(name);
948 if (name == null) {
949 name = method.name;
950 } else {
951 name = 'operator $name';
952 }
953 } 1047 }
954 1048
955 write('<strong>$name</strong>'); 1049 write('<strong>$name</strong>');
956 1050
957 // Named constructors. 1051 // Named constructors.
958 if (constructorName != null && constructorName != '') { 1052 if (method.constructorName != null && method.constructorName != '') {
959 write('.'); 1053 write('.');
960 write(constructorName); 1054 write(method.constructorName);
961 } 1055 }
962 1056
963 docParamList(type, method); 1057 docParamList(host, method.parameters());
964 1058
1059 var prefix = host is LibraryMirror ? '' : '${typeName(host)}.';
965 write(''' <a class="anchor-link" href="#${memberAnchor(method)}" 1060 write(''' <a class="anchor-link" href="#${memberAnchor(method)}"
966 title="Permalink to ${typeName(type)}.$name">#</a>'''); 1061 title="Permalink to $prefix$name">#</a>''');
967 writeln('</h4>'); 1062 writeln('</h4>');
968 1063
969 docCode(method.span, getMethodComment(method), showCode: true); 1064 docCode(method.location(), getMethodComment(method), showCode: true);
970 1065
971 writeln('</div>'); 1066 writeln('</div>');
972 } 1067 }
973 1068
974 /** Documents the field [field] of type [type]. */ 1069 /** Documents the field [field] of type [type]. */
975 void docField(Type type, FieldMember field) { 1070 void docField(ObjectMirror host, FieldMirror field) {
976 _totalMembers++; 1071 _totalMembers++;
977 _currentMember = field; 1072 _currentMember = field;
978 1073
979 writeln('<div class="field"><h4 id="${memberAnchor(field)}">'); 1074 writeln('<div class="field"><h4 id="${memberAnchor(field)}">');
980 1075
981 if (includeSource) { 1076 if (includeSource) {
982 writeln('<span class="show-code">Code</span>'); 1077 writeln('<span class="show-code">Code</span>');
983 } 1078 }
984 1079
985 if (field.isFinal) { 1080 if (field.isFinal) {
986 write('final '); 1081 write('final ');
987 } else if (field.type.name == 'Dynamic') { 1082 } else if (field.type().isDynamic) {
988 write('var '); 1083 write('var ');
989 } 1084 }
990 1085
991 annotateType(type, field.type); 1086 annotateType(host, field.type());
1087 var prefix = host is LibraryMirror ? '' : '${typeName(host)}.';
992 write( 1088 write(
993 ''' 1089 '''
994 <strong>${field.name}</strong> <a class="anchor-link" 1090 <strong>${field.simpleName()}</strong> <a class="anchor-link"
995 href="#${memberAnchor(field)}" 1091 href="#${memberAnchor(field)}"
996 title="Permalink to ${typeName(type)}.${field.name}">#</a> 1092 title="Permalink to $prefix${field.simpleName()}">#</a>
997 </h4> 1093 </h4>
998 '''); 1094 ''');
999 1095
1000 docCode(field.span, getFieldComment(field), showCode: true); 1096 docCode(field.location(), getFieldComment(field), showCode: true);
1001 writeln('</div>'); 1097 writeln('</div>');
1002 } 1098 }
1003 1099
1004 void docParamList(Type enclosingType, MethodMember member) { 1100 void docParamList(ObjectMirror enclosingType,
1101 List<ParameterMirror> parameters) {
1005 write('('); 1102 write('(');
1006 bool first = true; 1103 bool first = true;
1007 bool inOptionals = false; 1104 bool inOptionals = false;
1008 for (final parameter in member.parameters) { 1105 for (final parameter in parameters) {
1009 if (!first) write(', '); 1106 if (!first) write(', ');
1010 1107
1011 if (!inOptionals && parameter.isOptional) { 1108 if (!inOptionals && parameter.isOptional()) {
1012 write('['); 1109 write('[');
1013 inOptionals = true; 1110 inOptionals = true;
1014 } 1111 }
1015 1112
1016 annotateType(enclosingType, parameter.type, parameter.name); 1113 annotateType(enclosingType, parameter.type(), parameter.simpleName());
1017 1114
1018 // Show the default value for named optional parameters. 1115 // Show the default value for named optional parameters.
1019 if (parameter.isOptional && parameter.hasDefaultValue) { 1116 if (parameter.isOptional() && parameter.hasDefaultValue()) {
1020 write(' = '); 1117 write(' = ');
1021 // TODO(rnystrom): Using the definition text here is a bit cheap. 1118 // TODO(rnystrom): Using the definition text here is a bit cheap.
1022 // We really should be pretty-printing the AST so that if you have: 1119 // We really should be pretty-printing the AST so that if you have:
1023 // foo([arg = 1 + /* comment */ 2]) 1120 // foo([arg = 1 + /* comment */ 2])
1024 // the docs should just show: 1121 // the docs should just show:
1025 // foo([arg = 1 + 2]) 1122 // foo([arg = 1 + 2])
1026 // For now, we'll assume you don't do that. 1123 // For now, we'll assume you don't do that.
Bob Nystrom 2012/07/09 16:59:55 You can remove this comment now.
Johnni Winther 2012/07/12 08:51:39 Done.
1027 write(parameter.definition.value.span.text); 1124 write(parameter.defaultValue());
1028 } 1125 }
1029 1126
1030 first = false; 1127 first = false;
1031 } 1128 }
1032 1129
1033 if (inOptionals) write(']'); 1130 if (inOptionals) write(']');
1034 write(')'); 1131 write(')');
1035 } 1132 }
1036 1133
1037 /** 1134 /**
1038 * Documents the code contained within [span] with [comment]. If [showCode] 1135 * Documents the code contained within [span] with [comment]. If [showCode]
1039 * is `true` (and [includeSource] is set), also includes the source code. 1136 * is `true` (and [includeSource] is set), also includes the source code.
1040 */ 1137 */
1041 void docCode(SourceSpan span, String comment, [bool showCode = false]) { 1138 void docCode(Location location, String comment, [bool showCode = false]) {
1042 writeln('<div class="doc">'); 1139 writeln('<div class="doc">');
1043 if (comment != null) { 1140 if (comment != null) {
1044 writeln(comment); 1141 writeln(comment);
1045 } 1142 }
1046 1143
1047 if (includeSource && showCode) { 1144 if (includeSource && showCode) {
1048 writeln('<pre class="source">'); 1145 writeln('<pre class="source">');
1049 writeln(md.escapeHtml(unindentCode(span))); 1146 writeln(md.escapeHtml(unindentCode(location)));
1050 writeln('</pre>'); 1147 writeln('</pre>');
1051 } 1148 }
1052 1149
1053 writeln('</div>'); 1150 writeln('</div>');
1054 } 1151 }
1055 1152
1056 1153
1057 /** Get the doc comment associated with the given library. */ 1154 /** Get the doc comment associated with the given library. */
1058 String getLibraryComment(Library library) { 1155 String getLibraryComment(LibraryMirror library) {
1059 // Look for a comment for the entire library. 1156 // Look for a comment for the entire library.
1060 final comment = _comments.findLibrary(library.baseSource); 1157 final comment = _comments.findLibrary(library.location().source());
1061 if (comment != null) { 1158 if (comment != null) {
1062 return md.markdownToHtml(comment); 1159 return md.markdownToHtml(comment);
1063 } 1160 }
1064 return null; 1161 return null;
1065 } 1162 }
1066 1163
1067 /** Get the doc comment associated with the given type. */ 1164 /** Get the doc comment associated with the given type. */
1068 String getTypeComment(Type type) { 1165 String getTypeComment(TypeMirror type) {
1069 String comment = _comments.find(type.span); 1166 String comment = _comments.find(type.location());
1070 if (comment == null) return null; 1167 if (comment == null) return null;
1071 return commentToHtml(comment); 1168 return commentToHtml(comment);
1072 } 1169 }
1073 1170
1074 /** Get the doc comment associated with the given method. */ 1171 /** Get the doc comment associated with the given method. */
1075 String getMethodComment(MethodMember method) { 1172 String getMethodComment(MethodMirror method) {
1076 String comment = _comments.find(method.span); 1173 String comment = _comments.find(method.location());
1077 if (comment == null) return null; 1174 if (comment == null) return null;
1078 return commentToHtml(comment); 1175 return commentToHtml(comment);
1079 } 1176 }
1080 1177
1081 /** Get the doc comment associated with the given field. */ 1178 /** Get the doc comment associated with the given field. */
1082 String getFieldComment(FieldMember field) { 1179 String getFieldComment(FieldMirror field) {
1083 String comment = _comments.find(field.span); 1180 String comment = _comments.find(field.location());
1084 if (comment == null) return null; 1181 if (comment == null) return null;
1085 return commentToHtml(comment); 1182 return commentToHtml(comment);
1086 } 1183 }
1087 1184
1088 String commentToHtml(String comment) => md.markdownToHtml(comment); 1185 String commentToHtml(String comment) => md.markdownToHtml(comment);
1089 1186
1090 /** 1187 /**
1091 * Converts [fullPath] which is understood to be a full path from the root of 1188 * Converts [fullPath] which is understood to be a full path from the root of
1092 * the generated docs to one relative to the current file. 1189 * the generated docs to one relative to the current file.
1093 */ 1190 */
1094 String relativePath(String fullPath) { 1191 String relativePath(String fullPath) {
1095 // Don't make it relative if it's an absolute path. 1192 // Don't make it relative if it's an absolute path.
1096 if (isAbsolute(fullPath)) return fullPath; 1193 if (isAbsolute(fullPath)) return fullPath;
1097 1194
1098 // TODO(rnystrom): Walks all the way up to root each time. Shouldn't do 1195 // TODO(rnystrom): Walks all the way up to root each time. Shouldn't do
1099 // this if the paths overlap. 1196 // this if the paths overlap.
1100 return '${repeat('../', countOccurrences(_filePath, '/'))}$fullPath'; 1197 return '${repeat('../', countOccurrences(_filePath, '/'))}$fullPath';
1101 } 1198 }
1102 1199
1103 /** Gets whether or not the given URL is absolute or relative. */ 1200 /** Gets whether or not the given URL is absolute or relative. */
1104 bool isAbsolute(String url) { 1201 bool isAbsolute(String url) {
1105 // TODO(rnystrom): Why don't we have a nice type in the platform for this? 1202 // TODO(rnystrom): Why don't we have a nice type in the platform for this?
1106 // TODO(rnystrom): This is a bit hackish. We consider any URL that lacks 1203 // TODO(rnystrom): This is a bit hackish. We consider any URL that lacks
1107 // a scheme to be relative. 1204 // a scheme to be relative.
1108 return const RegExp(@'^\w+:').hasMatch(url); 1205 return const RegExp(@'^\w+:').hasMatch(url);
1109 } 1206 }
1110 1207
1111 /** Gets the URL to the documentation for [library]. */ 1208 /** Gets the URL to the documentation for [library]. */
1112 String libraryUrl(Library library) { 1209 String libraryUrl(LibraryMirror library) {
1113 return '${sanitize(library.name)}.html'; 1210 return '${sanitize(library.simpleName())}.html';
1114 } 1211 }
1115 1212
1116 /** Gets the URL for the documentation for [type]. */ 1213 /** Gets the URL for the documentation for [type]. */
1117 String typeUrl(Type type) { 1214 String typeUrl(ObjectMirror type) {
1118 if (type.isTop) return '${sanitize(type.library.name)}.html'; 1215 if (type is LibraryMirror) return '${sanitize(type.simpleName())}.html';
1216 assert (type is TypeMirror);
1119 // Always get the generic type to strip off any type parameters or 1217 // Always get the generic type to strip off any type parameters or
1120 // arguments. If the type isn't generic, genericType returns `this`, so it 1218 // arguments. If the type isn't generic, genericType returns `this`, so it
1121 // works for non-generic types too. 1219 // works for non-generic types too.
1122 return '${sanitize(type.library.name)}/${type.genericType.name}.html'; 1220 return '${sanitize(type.library().simpleName())}/'
1221 '${type.declaration.simpleName()}.html';
1123 } 1222 }
1124 1223
1125 /** Gets the URL for the documentation for [member]. */ 1224 /** Gets the URL for the documentation for [member]. */
1126 String memberUrl(Member member) { 1225 String memberUrl(MemberMirror member) {
1127 final typeUrl = typeUrl(member.declaringType); 1226 final url = typeUrl(member.surroundingDeclaration());
1128 if (!member.isConstructor) return '$typeUrl#${member.name}'; 1227 if (!member.isConstructor) return '$url#${member.simpleName()}';
1129 if (member.constructorName == '') return '$typeUrl#new:${member.name}'; 1228 assert (member is MethodMirror);
1130 return '$typeUrl#new:${member.name}.${member.constructorName}'; 1229 if (member.constructorName == '') return '$url#new:${member.simpleName()}';
1230 return '$url#new:${member.simpleName()}.${member.constructorName}';
1131 } 1231 }
1132 1232
1133 /** Gets the anchor id for the document for [member]. */ 1233 /** Gets the anchor id for the document for [member]. */
1134 String memberAnchor(Member member) { 1234 String memberAnchor(MemberMirror member) {
1135 return '${member.name}'; 1235 return '${member.simpleName()}';
1136 } 1236 }
1137 1237
1138 /** 1238 /**
1139 * Creates a hyperlink. Handles turning the [href] into an appropriate 1239 * Creates a hyperlink. Handles turning the [href] into an appropriate
1140 * relative path from the current file. 1240 * relative path from the current file.
1141 */ 1241 */
1142 String a(String href, String contents, [String css]) { 1242 String a(String href, String contents, [String css]) {
1143 // Mark outgoing external links, mainly so we can style them. 1243 // Mark outgoing external links, mainly so we can style them.
1144 final rel = isAbsolute(href) ? ' ref="external"' : ''; 1244 final rel = isAbsolute(href) ? ' ref="external"' : '';
1145 final cssClass = css == null ? '' : ' class="$css"'; 1245 final cssClass = css == null ? '' : ' class="$css"';
1146 return '<a href="${relativePath(href)}"$cssClass$rel>$contents</a>'; 1246 return '<a href="${relativePath(href)}"$cssClass$rel>$contents</a>';
1147 } 1247 }
1148 1248
1149 /** 1249 /**
1150 * Writes a type annotation for the given type and (optional) parameter name. 1250 * Writes a type annotation for the given type and (optional) parameter name.
1151 */ 1251 */
1152 annotateType(Type enclosingType, Type type, [String paramName = null]) { 1252 annotateType(ObjectMirror enclosingType,
1253 TypeMirror type,
1254 [String paramName = null]) {
1153 // Don't bother explicitly displaying Dynamic. 1255 // Don't bother explicitly displaying Dynamic.
1154 if (type.isVar) { 1256 if (type.isDynamic) {
1155 if (paramName !== null) write(paramName); 1257 if (paramName !== null) write(paramName);
1156 return; 1258 return;
1157 } 1259 }
1158 1260
1159 // For parameters, handle non-typedefed function types. 1261 // For parameters, handle non-typedefed function types.
1160 if (paramName !== null) { 1262 if (paramName !== null && type is FunctionTypeMirror) {
1161 final call = type.getCallMethod(); 1263 annotateType(enclosingType, type.returnType());
1162 if (call != null) { 1264 write(paramName);
1163 annotateType(enclosingType, call.returnType);
1164 write(paramName);
1165 1265
1166 docParamList(enclosingType, call); 1266 docParamList(enclosingType, type.parameters());
1167 return; 1267 return;
1168 }
1169 } 1268 }
1170 1269
1171 linkToType(enclosingType, type); 1270 linkToType(enclosingType, type);
1172 1271
1173 write(' '); 1272 write(' ');
1174 if (paramName !== null) write(paramName); 1273 if (paramName !== null) write(paramName);
1175 } 1274 }
1176 1275
1177 /** Writes a link to a human-friendly string representation for a type. */ 1276 /** Writes a link to a human-friendly string representation for a type. */
1178 linkToType(Type enclosingType, Type type) { 1277 linkToType(ObjectMirror enclosingType, TypeMirror type) {
1179 if (type is ParameterType) { 1278 if (type.isVoid) {
1180 // If we're using a type parameter within the body of a generic class then 1279 // Do not generate links for void
Bob Nystrom 2012/07/09 16:59:55 "." at end of comment.
Johnni Winther 2012/07/12 08:51:39 Done.
1181 // just link back up to the class. 1280 // TODO(johnniwinter): Generate span for specific style?
1182 write(a(typeUrl(enclosingType), type.name)); 1281 write('void');
1183 return; 1282 return;
1184 } 1283 }
1185 1284
1285 if (type.isTypeVariable) {
1286 // If we're using a type parameter within the body of a generic class then
1287 // just link back up to the class.
1288 write(a(typeUrl(enclosingType), type.simpleName()));
1289 return;
1290 }
1291
1292 assert(type is InterfaceMirror);
1293
1186 // Link to the type. 1294 // Link to the type.
1187 // Use .genericType to avoid writing the <...> here. 1295 if (includeLibrary(type.library())) {
1188 write(a(typeUrl(type), type.genericType.name)); 1296 write(a(typeUrl(type), type.simpleName()));
1297 } else {
1298 write(type.simpleName());
1299 }
1189 1300
1190 // See if it's a generic type. 1301 if (type.isDeclaration) {
1191 if (type.isGeneric) { 1302 // Avoid calling [:typeArguments():] on a declaration.
1192 // TODO(rnystrom): This relies on a weird corner case of frog. Currently,
1193 // the only time we get into this case is when we have a "raw" generic
1194 // that's been instantiated with Dynamic for all type arguments. It's kind
1195 // of strange that frog works that way, but we take advantage of it to
1196 // show raw types without any type arguments.
1197 return; 1303 return;
1198 } 1304 }
1199 1305
1200 // See if it's an instantiation of a generic type. 1306 // See if it's an instantiation of a generic type.
1201 final typeArgs = type.typeArgsInOrder; 1307 final typeArgs = type.typeArguments();
1202 if (typeArgs.length > 0) { 1308 if (typeArgs.length > 0) {
1203 write('&lt;'); 1309 write('&lt;');
1204 bool first = true; 1310 bool first = true;
1205 for (final arg in typeArgs) { 1311 for (final arg in typeArgs) {
1206 if (!first) write(', '); 1312 if (!first) write(', ');
1207 first = false; 1313 first = false;
1208 linkToType(enclosingType, arg); 1314 linkToType(enclosingType, arg);
1209 } 1315 }
1210 write('&gt;'); 1316 write('&gt;');
1211 } 1317 }
1212 } 1318 }
1213 1319
1214 /** Creates a linked cross reference to [type]. */ 1320 /** Creates a linked cross reference to [type]. */
1215 typeReference(Type type) { 1321 typeReference(InterfaceMirror type) {
1216 // TODO(rnystrom): Do we need to handle ParameterTypes here like 1322 // TODO(rnystrom): Do we need to handle ParameterTypes here like
1217 // annotation() does? 1323 // annotation() does?
1218 return a(typeUrl(type), typeName(type), css: 'crossref'); 1324 return a(typeUrl(type), typeName(type), css: 'crossref');
1219 } 1325 }
1220 1326
1221 /** Generates a human-friendly string representation for a type. */ 1327 /** Generates a human-friendly string representation for a type. */
1222 typeName(Type type, [bool showBounds = false]) { 1328 typeName(TypeMirror type, [bool showBounds = false]) {
1329 if (type.isVoid) {
1330 return 'void';
1331 }
Bob Nystrom 2012/07/09 16:59:55 The style guide allows single-line ifs for things
1332 if (type is TypeVariableMirror) {
1333 return type.simpleName();
1334 }
1335 assert (type is InterfaceMirror);
1336
1223 // See if it's a generic type. 1337 // See if it's a generic type.
1224 if (type.isGeneric) { 1338 if (type.isDeclaration) {
1225 final typeParams = []; 1339 final typeParams = [];
1226 for (final typeParam in type.genericType.typeParameters) { 1340 for (final typeParam in type.declaration.typeVariables()) {
1227 if (showBounds && 1341 if (showBounds &&
1228 (typeParam.extendsType != null) && 1342 (typeParam.bound() != null) &&
1229 !typeParam.extendsType.isObject) { 1343 !typeParam.bound().isObject) {
1230 final bound = typeName(typeParam.extendsType, showBounds: true); 1344 final bound = typeName(typeParam.bound(), showBounds: true);
1231 typeParams.add('${typeParam.name} extends $bound'); 1345 typeParams.add('${typeParam.simpleName()} extends $bound');
1232 } else { 1346 } else {
1233 typeParams.add(typeParam.name); 1347 typeParams.add(typeParam.simpleName());
1234 } 1348 }
1235 } 1349 }
1236 1350 if (typeParams.isEmpty()) {
1351 return type.simpleName();
1352 }
1237 final params = Strings.join(typeParams, ', '); 1353 final params = Strings.join(typeParams, ', ');
1238 return '${type.name}&lt;$params&gt;'; 1354 return '${type.simpleName()}&lt;$params&gt;';
1239 } 1355 }
1240 1356
1241 // See if it's an instantiation of a generic type. 1357 // See if it's an instantiation of a generic type.
1242 final typeArgs = type.typeArgsInOrder; 1358 final typeArgs = type.typeArguments();
1243 if (typeArgs.length > 0) { 1359 if (typeArgs.length > 0) {
1244 final args = Strings.join(map(typeArgs, (arg) => typeName(arg)), ', '); 1360 final args = Strings.join(typeArgs.map((arg) => typeName(arg)), ', ');
1245 return '${type.genericType.name}&lt;$args&gt;'; 1361 return '${type.declaration.simpleName()}&lt;$args&gt;';
1246 } 1362 }
1247 1363
1248 // Regular type. 1364 // Regular type.
1249 return type.name; 1365 return type.simpleName();
1250 } 1366 }
1251 1367
1252 /** 1368 /**
1253 * Remove leading indentation to line up with first line. 1369 * Remove leading indentation to line up with first line.
1254 */ 1370 */
1255 unindentCode(SourceSpan span) { 1371 unindentCode(Location span) {
1256 final column = getSpanColumn(span); 1372 final column = getSpanColumn(span);
1257 final lines = span.text.split('\n'); 1373 final lines = span.text().split('\n');
1258 // TODO(rnystrom): Dirty hack. 1374 // TODO(rnystrom): Dirty hack.
1259 for (var i = 1; i < lines.length; i++) { 1375 for (var i = 1; i < lines.length; i++) {
1260 lines[i] = unindent(lines[i], column); 1376 lines[i] = unindent(lines[i], column);
1261 } 1377 }
1262 1378
1263 final code = Strings.join(lines, '\n'); 1379 final code = Strings.join(lines, '\n');
1264 return code; 1380 return code;
1265 } 1381 }
1266 1382
1267 /** 1383 /**
1268 * Takes a string of Dart code and turns it into sanitized HTML. 1384 * Takes a string of Dart code and turns it into sanitized HTML.
1269 */ 1385 */
1270 formatCode(SourceSpan span) { 1386 formatCode(Location span) {
1271 final code = unindentCode(span); 1387 final code = unindentCode(span);
1272 1388
1273 // Syntax highlight. 1389 // Syntax highlight.
1274 return classifySource(new SourceFile('', code)); 1390 return classifySource(code);
1275 } 1391 }
1276 1392
1277 /** 1393 /**
1278 * This will be called whenever a doc comment hits a `[name]` in square 1394 * This will be called whenever a doc comment hits a `[name]` in square
1279 * brackets. It will try to figure out what the name refers to and link or 1395 * brackets. It will try to figure out what the name refers to and link or
1280 * style it appropriately. 1396 * style it appropriately.
1281 */ 1397 */
1282 md.Node resolveNameReference(String name, [Member member = null, 1398 md.Node resolveNameReference(String name,
1283 Type type = null, Library library = null]) { 1399 [MemberMirror member = null,
1400 ObjectMirror type = null,
1401 LibraryMirror library = null]) {
1284 makeLink(String href) { 1402 makeLink(String href) {
1285 final anchor = new md.Element.text('a', name); 1403 final anchor = new md.Element.text('a', name);
1286 anchor.attributes['href'] = relativePath(href); 1404 anchor.attributes['href'] = relativePath(href);
1287 anchor.attributes['class'] = 'crossref'; 1405 anchor.attributes['class'] = 'crossref';
1288 return anchor; 1406 return anchor;
1289 } 1407 }
1290 1408
1291 findMember(Type type, String memberName) {
1292 final member = type.members[memberName];
1293 if (member == null) return null;
1294
1295 // Special case: if the member we've resolved is a property (i.e. it wraps
1296 // a getter and/or setter then *that* member itself won't be on the docs,
1297 // just the getter or setter will be. So pick one of those to link to.
1298 if (member.isProperty) {
1299 return member.canGet ? member.getter : member.setter;
1300 }
1301
1302 return member;
1303 }
1304
1305 // See if it's a parameter of the current method. 1409 // See if it's a parameter of the current method.
1306 if (member != null) { 1410 if (member is MethodMirror) {
1307 for (final parameter in member.parameters) { 1411 for (final parameter in member.parameters()) {
1308 if (parameter.name == name) { 1412 if (parameter.simpleName() == name) {
1309 final element = new md.Element.text('span', name); 1413 final element = new md.Element.text('span', name);
1310 element.attributes['class'] = 'param'; 1414 element.attributes['class'] = 'param';
1311 return element; 1415 return element;
1312 } 1416 }
1313 } 1417 }
1314 } 1418 }
1315 1419
1316 // See if it's another member of the current type. 1420 // See if it's another member of the current type.
1317 if (type != null) { 1421 if (type != null) {
1318 final member = findMember(type, name); 1422 final member = findMirror(type.declaredMembers(), name);
1319 if (member != null) { 1423 if (member != null) {
1320 return makeLink(memberUrl(member)); 1424 return makeLink(memberUrl(member));
1321 } 1425 }
1322 } 1426 }
1323 1427
1324 // See if it's another type or a member of another type in the current 1428 // See if it's another type or a member of another type in the current
1325 // library. 1429 // library.
1326 if (library != null) { 1430 if (library != null) {
1327 // See if it's a constructor 1431 // See if it's a constructor
1328 final constructorLink = (() { 1432 final constructorLink = (() {
1329 final match = new RegExp(@'new (\w+)(?:\.(\w+))?').firstMatch(name); 1433 final match = new RegExp(@'new ([\w$]+)(?:\.([\w$]+))?').firstMatch(name );
Bob Nystrom 2012/07/09 16:59:55 Long line.
Johnni Winther 2012/07/12 08:51:39 Done.
1330 if (match == null) return; 1434 if (match == null) return;
1331 final type = library.types[match[1]]; 1435 final type = findMirror(library.types(), match[1]);
1332 if (type == null) return; 1436 if (type == null) return;
1333 final constructor = type.getConstructor( 1437 final constructor =
1334 match[2] == null ? '' : match[2]); 1438 findMirror(type.constructors(),
1439 match[2] == null ? '' : match[2]);
1335 if (constructor == null) return; 1440 if (constructor == null) return;
1336 return makeLink(memberUrl(constructor)); 1441 return makeLink(memberUrl(constructor));
1337 })(); 1442 })();
1338 if (constructorLink != null) return constructorLink; 1443 if (constructorLink != null) return constructorLink;
1339 1444
1340 // See if it's a member of another type 1445 // See if it's a member of another type
1341 final foreignMemberLink = (() { 1446 final foreignMemberLink = (() {
1342 final match = new RegExp(@'(\w+)\.(\w+)').firstMatch(name); 1447 final match = new RegExp(@'([\w$]+)\.([\w$]+)').firstMatch(name);
1343 if (match == null) return; 1448 if (match == null) return;
1344 final type = library.types[match[1]]; 1449 final type = findMirror(library.types(), match[1]);
1345 if (type == null) return; 1450 if (type == null) return;
1346 final member = findMember(type, match[2]); 1451 final member = findMirror(type.declaredMembers(), match[2]);
1347 if (member == null) return; 1452 if (member == null) return;
1348 return makeLink(memberUrl(member)); 1453 return makeLink(memberUrl(member));
1349 })(); 1454 })();
1350 if (foreignMemberLink != null) return foreignMemberLink; 1455 if (foreignMemberLink != null) return foreignMemberLink;
1351 1456
1352 final type = library.types[name]; 1457 final type = findMirror(library.types(), name);
1353 if (type != null) { 1458 if (type != null) {
1354 return makeLink(typeUrl(type)); 1459 return makeLink(typeUrl(type));
1355 } 1460 }
1356 1461
1357 // See if it's a top-level member in the current library. 1462 // See if it's a top-level member in the current library.
1358 final member = findMember(library.topType, name); 1463 final member = findMirror(library.declaredMembers(), name);
1359 if (member != null) { 1464 if (member != null) {
1360 return makeLink(memberUrl(member)); 1465 return makeLink(memberUrl(member));
1361 } 1466 }
1362 } 1467 }
1363 1468
1364 // TODO(rnystrom): Should also consider: 1469 // TODO(rnystrom): Should also consider:
1365 // * Names imported by libraries this library imports. 1470 // * Names imported by libraries this library imports.
1366 // * Type parameters of the enclosing type. 1471 // * Type parameters of the enclosing type.
1367 1472
1368 return new md.Element.text('code', name); 1473 return new md.Element.text('code', name);
1369 } 1474 }
1370 1475
1371 // TODO(rnystrom): Move into SourceSpan? 1476 int getSpanColumn(Location span) {
Bob Nystrom 2012/07/09 16:59:55 This seems like something Location should support
Johnni Winther 2012/07/12 08:51:39 Moved to mirrors_util.dart instead. We haven't set
1372 int getSpanColumn(SourceSpan span) { 1477 String text = span.source().text();
1373 final line = span.file.getLine(span.start); 1478 int index = span.start()-1;
1374 return span.file.getColumn(line, span.start); 1479 var column = 0;
1480 while (0 <= index && index < text.length) {
1481 var charCode = text.charCodeAt(index);
1482 if (charCode == $CR || charCode == $LF) {
1483 break;
1484 }
1485 index--;
1486 column++;
1487 }
1488 return column;
1375 } 1489 }
1376 1490
1377 generateAppCacheManifest() { 1491 generateAppCacheManifest() {
1378 print('Generating app cache manifest from output $outputDir'); 1492 print('Generating app cache manifest from output $outputDir');
1379 startFile('appcache.manifest'); 1493 startFile('appcache.manifest');
1380 write("CACHE MANIFEST\n\n"); 1494 write("CACHE MANIFEST\n\n");
1381 write("# VERSION: ${new Date.now()}\n\n"); 1495 write("# VERSION: ${new Date.now()}\n\n");
1382 write("NETWORK:\n*\n\n"); 1496 write("NETWORK:\n*\n\n");
1383 write("CACHE:\n"); 1497 write("CACHE:\n");
1384 var toCache = new Directory(outputDir); 1498 var toCache = new Directory(outputDir);
1385 var pathPrefix = new File(outputDir).fullPathSync(); 1499 var pathPrefix = new File(outputDir).fullPathSync();
1386 var pathPrefixLength = pathPrefix.length; 1500 var pathPrefixLength = pathPrefix.length;
1387 toCache.onFile = (filename) { 1501 toCache.onFile = (filename) {
1388 if (filename.endsWith('appcache.manifest')) { 1502 if (filename.endsWith('appcache.manifest')) {
1389 return; 1503 return;
1390 } 1504 }
1391 var relativePath = filename.substring(pathPrefixLength + 1); 1505 var relativePath = filename.substring(pathPrefixLength + 1);
1392 write("$relativePath\n"); 1506 write("$relativePath\n");
1393 }; 1507 };
1394 toCache.onDone = (done) => endFile(); 1508 toCache.onDone = (done) => endFile();
1395 toCache.list(recursive: true); 1509 toCache.list(recursive: true);
1396 } 1510 }
1511
1512 /**
1513 * Returns [:true:] if [type] should be regarded as an exception.
1514 */
1515 bool isException(TypeMirror type) {
1516 return type.simpleName().endsWith('Exception');
1517 }
1397 } 1518 }
1519
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698