OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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(' > '); | 875 write(' > '); |
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('<'); | 1309 write('<'); |
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('>'); | 1316 write('>'); |
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}<$params>'; | 1354 return '${type.simpleName()}<$params>'; |
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}<$args>'; | 1361 return '${type.declaration.simpleName()}<$args>'; |
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 | |
OLD | NEW |