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