| OLD | NEW |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2011, 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 /** An awesome documentation generator. */ | 5 /** |
| 6 * To use it, from this directory, run: |
| 7 * |
| 8 * $ dartdoc <path to .dart file> |
| 9 * |
| 10 * This will create a "docs" directory with the docs for your libraries. To do |
| 11 * so, dartdoc parses that library and every library it imports. From each |
| 12 * library, it parses all classes and members, finds the associated doc |
| 13 * comments and builds crosslinked docs from them. |
| 14 */ |
| 6 #library('dartdoc'); | 15 #library('dartdoc'); |
| 7 | 16 |
| 8 #import('../../frog/lang.dart'); | 17 #import('../../frog/lang.dart'); |
| 9 #import('../../frog/file_system.dart'); | 18 #import('../../frog/file_system.dart'); |
| 10 #import('../../frog/file_system_node.dart'); | 19 #import('../../frog/file_system_node.dart'); |
| 20 #import('../markdown/lib.dart', prefix: 'md'); |
| 11 | 21 |
| 12 #source('classify.dart'); | 22 #source('classify.dart'); |
| 13 | 23 |
| 14 /** Path to corePath library. */ | 24 /** Path to corePath library. */ |
| 15 final corePath = 'lib'; | 25 final corePath = 'lib'; |
| 16 | 26 |
| 17 /** Path to generate html files into. */ | 27 /** Path to generate html files into. */ |
| 18 final outdir = 'docs'; | 28 final outdir = 'docs'; |
| 19 | 29 |
| 30 /** Set to `true` to include the source code in the generated docs. */ |
| 31 bool includeSource = true; |
| 32 |
| 20 /** Special comment position used to store the library-level doc comment. */ | 33 /** Special comment position used to store the library-level doc comment. */ |
| 21 final _libraryDoc = -1; | 34 final _libraryDoc = -1; |
| 22 | 35 |
| 23 /** The file currently being written to. */ | 36 /** The file currently being written to. */ |
| 24 StringBuffer _file; | 37 StringBuffer _file; |
| 25 | 38 |
| 39 /** The library that we're currently generating docs for. */ |
| 40 Library _currentLibrary; |
| 41 |
| 42 /** The type that we're currently generating docs for. */ |
| 43 Type _currentType; |
| 44 |
| 45 /** The member that we're currently generating docs for. */ |
| 46 Member _currentMember; |
| 47 |
| 26 /** | 48 /** |
| 27 * The cached lookup-table to associate doc comments with spans. The outer map | 49 * The cached lookup-table to associate doc comments with spans. The outer map |
| 28 * is from filenames to doc comments in that file. The inner map maps from the | 50 * is from filenames to doc comments in that file. The inner map maps from the |
| 29 * token positions to doc comments. Each position is the starting offset of the | 51 * token positions to doc comments. Each position is the starting offset of the |
| 30 * next non-comment token *following* the doc comment. For example, the position | 52 * next non-comment token *following* the doc comment. For example, the position |
| 31 * for this comment would be the position of the "Map" token below. | 53 * for this comment would be the position of the "Map" token below. |
| 32 */ | 54 */ |
| 33 Map<String, Map<int, String>> _comments; | 55 Map<String, Map<int, String>> _comments; |
| 34 | 56 |
| 35 int _totalLibraries = 0; | 57 int _totalLibraries = 0; |
| 36 int _totalTypes = 0; | 58 int _totalTypes = 0; |
| 37 int _totalMembers = 0; | 59 int _totalMembers = 0; |
| 38 | 60 |
| 39 FileSystem files; | 61 FileSystem files; |
| 40 | 62 |
| 41 /** | 63 /** |
| 42 * Run this from the frog/samples directory. Before running, you need | 64 * Run this from the `utils/dartdoc` directory. |
| 43 * to create a docs dir with 'mkdir docs' - since Dart currently doesn't | |
| 44 * support creating new directories. | |
| 45 */ | 65 */ |
| 46 void main() { | 66 void main() { |
| 47 // The entrypoint of the library to generate docs for. | 67 // The entrypoint of the library to generate docs for. |
| 48 final libPath = process.argv[2]; | 68 final libPath = process.argv[2]; |
| 49 | 69 |
| 50 files = new NodeFileSystem(); | 70 files = new NodeFileSystem(); |
| 51 parseOptions('../../frog', [] /* args */, files); | 71 parseOptions('../../frog', [] /* args */, files); |
| 52 | 72 |
| 73 // Patch in support for [:...:]-style code to the markdown parser. |
| 74 // TODO(rnystrom): Markdown already has syntax for this. Phase this out? |
| 75 md.InlineParser.syntaxes.insertRange(0, 1, |
| 76 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); |
| 77 |
| 78 md.setImplicitLinkResolver(resolveNameReference); |
| 79 |
| 53 final elapsed = time(() { | 80 final elapsed = time(() { |
| 54 initializeDartDoc(); | 81 initializeDartDoc(); |
| 55 | 82 |
| 56 initializeWorld(files); | 83 initializeWorld(files); |
| 57 | 84 |
| 58 world.processScript(libPath); | 85 world.processScript(libPath); |
| 59 world.resolveAll(); | 86 world.resolveAll(); |
| 60 | 87 |
| 61 // Clean the output directory. | 88 // Clean the output directory. |
| 62 if (files.fileExists(outdir)) { | 89 if (files.fileExists(outdir)) { |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 134 <div class="content"> | 161 <div class="content"> |
| 135 <ul> | 162 <ul> |
| 136 '''); | 163 '''); |
| 137 | 164 |
| 138 final sorted = new List<Library>.from(libraries); | 165 final sorted = new List<Library>.from(libraries); |
| 139 sorted.sort((a, b) => a.name.compareTo(b.name)); | 166 sorted.sort((a, b) => a.name.compareTo(b.name)); |
| 140 | 167 |
| 141 for (final library in sorted) { | 168 for (final library in sorted) { |
| 142 writeln( | 169 writeln( |
| 143 ''' | 170 ''' |
| 144 <li><a href="${sanitize(library.name)}.html"> | 171 <li><a href="${libraryUrl(library)}">Library ${library.name}</a></li> |
| 145 Library ${library.name}</a> | |
| 146 </li> | |
| 147 '''); | 172 '''); |
| 148 } | 173 } |
| 149 | 174 |
| 150 writeln( | 175 writeln( |
| 151 ''' | 176 ''' |
| 152 </ul> | 177 </ul> |
| 153 </div> | 178 </div> |
| 154 </body></html> | 179 </body></html> |
| 155 '''); | 180 '''); |
| 156 | 181 |
| 157 endFile('$outdir/index.html'); | 182 endFile('$outdir/index.html'); |
| 158 } | 183 } |
| 159 | 184 |
| 160 docLibrary(Library library) { | 185 docLibrary(Library library) { |
| 161 _totalLibraries++; | 186 _totalLibraries++; |
| 187 _currentLibrary = library; |
| 162 | 188 |
| 163 startFile(); | 189 startFile(); |
| 164 writeln( | 190 writeln( |
| 165 ''' | 191 ''' |
| 166 <html> | 192 <html> |
| 167 <head> | 193 <head> |
| 168 <title>${library.name}</title> | 194 <title>${library.name}</title> |
| 169 <link rel="stylesheet" type="text/css" href="styles.css" /> | 195 <link rel="stylesheet" type="text/css" href="styles.css" /> |
| 170 <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700,8
00" rel="stylesheet" type="text/css"> | 196 <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700,8
00" rel="stylesheet" type="text/css"> |
| 171 <script src="interact.js"></script> | 197 <script src="interact.js"></script> |
| 172 </head> | 198 </head> |
| 173 <body> | 199 <body> |
| 174 <div class="content"> | 200 <div class="content"> |
| 175 <h1>Library <strong>${library.name}</strong></h1> | 201 <h1>Library <strong>${library.name}</strong></h1> |
| 176 '''); | 202 '''); |
| 177 | 203 |
| 178 bool needsSeparator = false; | 204 bool needsSeparator = false; |
| 179 | 205 |
| 180 // Look for a comment for the entire library. | 206 // Look for a comment for the entire library. |
| 181 final comment = findCommentInFile(library.baseSource, _libraryDoc); | 207 final comment = findCommentInFile(library.baseSource, _libraryDoc); |
| 182 if (comment != null) { | 208 if (comment != null) { |
| 183 writeln('<div class="doc"><p>$comment</p></div>'); | 209 final html = md.markdownToHtml(comment); |
| 210 writeln('<div class="doc">$html</div>'); |
| 184 needsSeparator = true; | 211 needsSeparator = true; |
| 185 } | 212 } |
| 186 | 213 |
| 187 for (final type in library.types.getValues()) { | 214 for (final type in orderValuesByKeys(library.types)) { |
| 215 // Skip private types (for now at least). |
| 216 if ((type.name != null) && type.name.startsWith('_')) continue; |
| 217 |
| 188 if (needsSeparator) writeln('<hr/>'); | 218 if (needsSeparator) writeln('<hr/>'); |
| 189 if (docType(type)) needsSeparator = true; | 219 if (docType(type)) needsSeparator = true; |
| 190 } | 220 } |
| 191 | 221 |
| 192 writeln( | 222 writeln( |
| 193 ''' | 223 ''' |
| 194 </div> | 224 </div> |
| 195 </body></html> | 225 </body></html> |
| 196 '''); | 226 '''); |
| 197 | 227 |
| 198 endFile('$outdir/${sanitize(library.name)}.html'); | 228 endFile('$outdir/${sanitize(library.name)}.html'); |
| 199 } | 229 } |
| 200 | 230 |
| 201 /** | 231 /** |
| 202 * Documents [Type]. Handles top-level members if given an unnamed Type. | 232 * Documents [type]. Handles top-level members if given an unnamed Type. |
| 203 * Returns [:true:] if it wrote anything. | 233 * Returns `true` if it wrote anything. |
| 204 */ | 234 */ |
| 205 bool docType(Type type) { | 235 bool docType(Type type) { |
| 206 _totalTypes++; | 236 _totalTypes++; |
| 237 _currentType = type; |
| 207 | 238 |
| 208 bool wroteSomething = false; | 239 bool wroteSomething = false; |
| 209 | 240 |
| 210 if (type.name != null) { | 241 if (type.name != null) { |
| 242 final name = typeName(type); |
| 243 |
| 211 write( | 244 write( |
| 212 ''' | 245 ''' |
| 213 <h2 id="${type.name}"> | 246 <h2 id="${typeAnchor(type)}"> |
| 214 ${type.isClass ? "Class" : "Interface"} <strong>${type.name}</strong> | 247 ${type.isClass ? "Class" : "Interface"} <strong>$name</strong> |
| 215 <a class="anchor-link" href="#${type.name}" | 248 <a class="anchor-link" href="${typeUrl(type)}" |
| 216 title="Permalink to ${type.name}">#</a> | 249 title="Permalink to $name">#</a> |
| 217 </h2> | 250 </h2> |
| 218 '''); | 251 '''); |
| 219 | 252 |
| 220 docInheritance(type); | 253 docInheritance(type); |
| 221 docCode(type.span); | 254 docCode(type.span); |
| 222 docConstructors(type); | 255 docConstructors(type); |
| 223 | 256 |
| 224 wroteSomething = true; | 257 wroteSomething = true; |
| 225 } | 258 } |
| 226 | 259 |
| 227 // Collect the different kinds of members. | 260 // Collect the different kinds of members. |
| 228 final methods = []; | 261 final methods = []; |
| 229 final fields = []; | 262 final fields = []; |
| 230 | 263 |
| 231 for (final member in orderValuesByKeys(type.members)) { | 264 for (final member in orderValuesByKeys(type.members)) { |
| 232 if (member.isMethod && | 265 if (member.isMethod && |
| 233 (member.definition != null) && | 266 (member.definition != null) && |
| 234 !member.name.startsWith('_')) { | 267 !member.name.startsWith('_')) { |
| 235 methods.add(member); | 268 methods.add(member); |
| 236 } else if (member.isProperty) { | 269 } else if (member.isProperty) { |
| 237 if (member.canGet) methods.add(member.getter); | 270 if (member.canGet) methods.add(member.getter); |
| 238 if (member.canSet) methods.add(member.setter); | 271 if (member.canSet) methods.add(member.setter); |
| 239 } else if (member.isField && !member.name.startsWith('_')) { | 272 } else if (member.isField && !member.name.startsWith('_')) { |
| 240 fields.add(member); | 273 fields.add(member); |
| 241 } | 274 } |
| 242 } | 275 } |
| 243 | 276 |
| 244 if (methods.length > 0) { | 277 if (methods.length > 0) { |
| 245 writeln('<h3>Methods</h3>'); | 278 writeln('<h3>Methods</h3>'); |
| 246 for (final method in methods) docMethod(type.name, method); | 279 for (final method in methods) docMethod(type, method); |
| 247 } | 280 } |
| 248 | 281 |
| 249 if (fields.length > 0) { | 282 if (fields.length > 0) { |
| 250 writeln('<h3>Fields</h3>'); | 283 writeln('<h3>Fields</h3>'); |
| 251 for (final field in fields) docField(type.name, field); | 284 for (final field in fields) docField(type, field); |
| 252 } | 285 } |
| 253 | 286 |
| 254 return wroteSomething || methods.length > 0 || fields.length > 0; | 287 return wroteSomething || methods.length > 0 || fields.length > 0; |
| 255 } | 288 } |
| 256 | 289 |
| 257 /** Document the superclass and superinterfaces of [Type]. */ | 290 /** Document the superclass and superinterfaces of [Type]. */ |
| 258 docInheritance(Type type) { | 291 docInheritance(Type type) { |
| 259 // Show the superclass and superinterface(s). | 292 // Show the superclass and superinterface(s). |
| 260 if ((type.parent != null) && (type.parent.isObject) || | 293 final isSubclass = (type.parent != null) && !type.parent.isObject; |
| 261 (type.interfaces != null && type.interfaces.length > 0)) { | 294 |
| 295 if (isSubclass || (type.interfaces != null && type.interfaces.length > 0)) { |
| 262 writeln('<p>'); | 296 writeln('<p>'); |
| 263 | 297 |
| 264 if (type.parent != null) { | 298 if (isSubclass) { |
| 265 write('Extends ${typeRef(type.parent)}. '); | 299 write('Extends ${typeReference(type.parent)}. '); |
| 266 } | 300 } |
| 267 | 301 |
| 268 if (type.interfaces != null) { | 302 if (type.interfaces != null) { |
| 269 final interfaces = []; | |
| 270 switch (type.interfaces.length) { | 303 switch (type.interfaces.length) { |
| 271 case 0: | 304 case 0: |
| 272 // Do nothing. | 305 // Do nothing. |
| 273 break; | 306 break; |
| 274 | 307 |
| 275 case 1: | 308 case 1: |
| 276 write('Implements ${typeRef(type.interfaces[0])}.'); | 309 write('Implements ${typeReference(type.interfaces[0])}.'); |
| 277 break; | 310 break; |
| 278 | 311 |
| 279 case 2: | 312 case 2: |
| 280 write('''Implements ${typeRef(type.interfaces[0])} and | 313 write('''Implements ${typeReference(type.interfaces[0])} and |
| 281 ${typeRef(type.interfaces[1])}.'''); | 314 ${typeReference(type.interfaces[1])}.'''); |
| 282 break; | 315 break; |
| 283 | 316 |
| 284 default: | 317 default: |
| 285 write('Implements '); | 318 write('Implements '); |
| 286 for (final i = 0; i < type.interfaces.length; i++) { | 319 for (final i = 0; i < type.interfaces.length; i++) { |
| 287 write('${typeRef(type.interfaces[i])}'); | 320 write('${typeReference(type.interfaces[i])}'); |
| 288 if (i < type.interfaces.length - 1) { | 321 if (i < type.interfaces.length - 2) { |
| 289 write(', '); | 322 write(', '); |
| 290 } else { | 323 } else if (i < type.interfaces.length - 1) { |
| 291 write(' and '); | 324 write(', and '); |
| 292 } | 325 } |
| 293 } | 326 } |
| 294 write('.'); | 327 write('.'); |
| 295 break; | 328 break; |
| 296 } | 329 } |
| 297 } | 330 } |
| 298 } | 331 } |
| 299 } | 332 } |
| 300 | 333 |
| 301 /** Document the constructors for [Type], if any. */ | 334 /** Document the constructors for [Type], if any. */ |
| 302 docConstructors(Type type) { | 335 docConstructors(Type type) { |
| 303 if (type.constructors.length > 0) { | 336 if (type.constructors.length > 0) { |
| 304 writeln('<h3>Constructors</h3>'); | 337 writeln('<h3>Constructors</h3>'); |
| 305 for (final name in type.constructors.getKeys()) { | 338 for (final name in type.constructors.getKeys()) { |
| 306 final constructor = type.constructors[name]; | 339 final constructor = type.constructors[name]; |
| 307 docMethod(type.name, constructor, namedConstructor: name); | 340 docMethod(type, constructor, constructorName: name); |
| 308 } | 341 } |
| 309 } | 342 } |
| 310 } | 343 } |
| 311 | 344 |
| 312 /** | 345 /** |
| 313 * Documents the [method] in a type named [typeName]. Handles all kinds of | 346 * Documents the [method] in type [type]. Handles all kinds of methods |
| 314 * methods including getters, setters, and constructors. | 347 * including getters, setters, and constructors. |
| 315 */ | 348 */ |
| 316 docMethod(String typeName, MethodMember method, | 349 docMethod(Type type, MethodMember method, [String constructorName = null]) { |
| 317 [String namedConstructor = null]) { | |
| 318 _totalMembers++; | 350 _totalMembers++; |
| 351 _currentMember = method; |
| 319 | 352 |
| 320 writeln( | 353 writeln('<div class="method"><h4 id="${memberAnchor(method)}">'); |
| 321 ''' | |
| 322 <div class="method"><h4 id="$typeName.${method.name}"> | |
| 323 <span class="show-code">Code</span> | |
| 324 '''); | |
| 325 | 354 |
| 326 // A null typeName means it's a top-level definition which is implicitly | 355 if (includeSource) { |
| 327 // static so doesn't need to annotate it. | 356 writeln('<span class="show-code">Code</span>'); |
| 328 if (method.isStatic && (typeName != null)) { | 357 } |
| 358 |
| 359 if (method.isStatic && !type.isTop) { |
| 329 write('static '); | 360 write('static '); |
| 330 } | 361 } |
| 331 | 362 |
| 332 if (method.isConstructor) { | 363 if (method.isConstructor) { |
| 333 write(method.isConst ? 'const ' : 'new '); | 364 write(method.isConst ? 'const ' : 'new '); |
| 334 } | 365 } |
| 335 | 366 |
| 336 if (namedConstructor == null) { | 367 if (constructorName == null) { |
| 337 write(optionalTypeRef(method.returnType)); | 368 write(annotation(type, method.returnType)); |
| 338 } | 369 } |
| 339 | 370 |
| 340 // Translate specially-named methods: getters, setters, operators. | 371 // Translate specially-named methods: getters, setters, operators. |
| 341 var name = method.name; | 372 var name = method.name; |
| 342 if (name.startsWith('get\$')) { | 373 if (name.startsWith('get\$')) { |
| 343 // Getter. | 374 // Getter. |
| 344 name = 'get ${name.substring(4)}'; | 375 name = 'get ${name.substring(4)}'; |
| 345 } else if (name.startsWith('set\$')) { | 376 } else if (name.startsWith('set\$')) { |
| 346 // Setter. | 377 // Setter. |
| 347 name = 'set ${name.substring(4)}'; | 378 name = 'set ${name.substring(4)}'; |
| 348 } else { | 379 } else { |
| 349 // See if it's an operator. | 380 // See if it's an operator. |
| 350 name = TokenKind.rawOperatorFromMethod(name); | 381 name = TokenKind.rawOperatorFromMethod(name); |
| 351 if (name == null) { | 382 if (name == null) { |
| 352 name = method.name; | 383 name = method.name; |
| 353 } else { | 384 } else { |
| 354 name = 'operator $name'; | 385 name = 'operator $name'; |
| 355 } | 386 } |
| 356 } | 387 } |
| 357 | 388 |
| 358 write('<strong>$name</strong>'); | 389 write('<strong>$name</strong>'); |
| 359 | 390 |
| 360 // Named constructors. | 391 // Named constructors. |
| 361 if (namedConstructor != null && namedConstructor != '') { | 392 if (constructorName != null && constructorName != '') { |
| 362 write('.'); | 393 write('.'); |
| 363 write(namedConstructor); | 394 write(constructorName); |
| 364 } | 395 } |
| 365 | 396 |
| 366 write('('); | 397 write('('); |
| 367 final paramList = []; | 398 final parameters = map(method.parameters, |
| 368 if (method.parameters == null) print(method.name); | 399 (p) => '${annotation(type, p.type)}${p.name}'); |
| 369 for (final p in method.parameters) { | 400 write(Strings.join(parameters, ', ')); |
| 370 paramList.add('${optionalTypeRef(p.type)}${p.name}'); | |
| 371 } | |
| 372 write(Strings.join(paramList, ", ")); | |
| 373 write(')'); | 401 write(')'); |
| 374 | 402 |
| 375 write(''' <a class="anchor-link" href="#$typeName.${method.name}" | 403 write(''' <a class="anchor-link" href="#${memberAnchor(method)}" |
| 376 title="Permalink to $typeName.$name">#</a>'''); | 404 title="Permalink to ${type.name}.$name">#</a>'''); |
| 377 writeln('</h4>'); | 405 writeln('</h4>'); |
| 378 | 406 |
| 379 docCode(method.span, showCode: true); | 407 docCode(method.span, showCode: true); |
| 380 | 408 |
| 381 writeln('</div>'); | 409 writeln('</div>'); |
| 382 } | 410 } |
| 383 | 411 |
| 384 /** Documents the field [field] in a type named [typeName]. */ | 412 /** Documents the field [field] of type [type]. */ |
| 385 docField(String typeName, FieldMember field) { | 413 docField(Type type, FieldMember field) { |
| 386 _totalMembers++; | 414 _totalMembers++; |
| 415 _currentMember = field; |
| 387 | 416 |
| 388 writeln( | 417 writeln('<div class="field"><h4 id="${memberAnchor(field)}">'); |
| 389 ''' | |
| 390 <div class="field"><h4 id="$typeName.${field.name}"> | |
| 391 <span class="show-code">Code</span> | |
| 392 '''); | |
| 393 | 418 |
| 394 // A null typeName means it's a top-level definition which is implicitly | 419 if (includeSource) { |
| 395 // static so doesn't need to annotate it. | 420 writeln('<span class="show-code">Code</span>'); |
| 396 if (field.isStatic && (typeName != null)) { | 421 } |
| 422 |
| 423 if (field.isStatic && !type.isTop) { |
| 397 write('static '); | 424 write('static '); |
| 398 } | 425 } |
| 399 | 426 |
| 400 if (field.isFinal) { | 427 if (field.isFinal) { |
| 401 write('final '); | 428 write('final '); |
| 402 } else if (field.type.name == 'Dynamic') { | 429 } else if (field.type.name == 'Dynamic') { |
| 403 write('var '); | 430 write('var '); |
| 404 } | 431 } |
| 405 | 432 |
| 406 write(optionalTypeRef(field.type)); | 433 write(annotation(type, field.type)); |
| 407 write( | 434 write( |
| 408 ''' | 435 ''' |
| 409 <strong>${field.name}</strong> <a class="anchor-link" | 436 <strong>${field.name}</strong> <a class="anchor-link" |
| 410 href="#$typeName.${field.name}" | 437 href="#${memberUrl(field)}" |
| 411 title="Permalink to $typeName.${field.name}">#</a> | 438 title="Permalink to ${type.name}.${field.name}">#</a> |
| 412 </h4> | 439 </h4> |
| 413 '''); | 440 '''); |
| 414 | 441 |
| 415 docCode(field.span, showCode: true); | 442 docCode(field.span, showCode: true); |
| 416 writeln('</div>'); | 443 writeln('</div>'); |
| 417 } | 444 } |
| 418 | 445 |
| 419 /** | 446 /** Generates a human-friendly string representation for a type. */ |
| 420 * Writes a type annotation for [type]. Will hyperlink it to that type's | 447 typeName(Type type) { |
| 421 * documentation if possible. | 448 // See if it's a generic type. |
| 422 */ | 449 if (type.isGeneric) { |
| 423 typeRef(Type type) { | 450 final typeParams = type.genericType.typeParameters; |
| 424 if (type.library != null) { | 451 final params = Strings.join(map(typeParams, (p) => p.name), ', '); |
| 425 final library = sanitize(type.library.name); | 452 return '${type.name}<$params>'; |
| 426 return '<a href="${library}.html#${type.name}">${type.name}</a>'; | |
| 427 } else { | |
| 428 return type.name; | |
| 429 } | 453 } |
| 454 |
| 455 // See if it's an instantiation of a generic type. |
| 456 final typeArgs = type.typeArgsInOrder; |
| 457 if (typeArgs != null) { |
| 458 final args = Strings.join(map(typeArgs, typeName), ', '); |
| 459 return '${type.genericType.name}<$args>'; |
| 460 } |
| 461 |
| 462 // Regular type. |
| 463 return type.name; |
| 464 } |
| 465 |
| 466 /** Gets the URL to the documentation for [library]. */ |
| 467 libraryUrl(Library library) => '${sanitize(library.name)}.html'; |
| 468 |
| 469 /** Gets the URL for the documentation for [type]. */ |
| 470 typeUrl(Type type) => '${libraryUrl(type.library)}#${typeAnchor(type)}'; |
| 471 |
| 472 /** Gets the URL for the documentation for [member]. */ |
| 473 memberUrl(Member member) => '${typeUrl(member.declaringType)}-${member.name}'; |
| 474 |
| 475 /** Gets the anchor id for the document for [type]. */ |
| 476 typeAnchor(Type type) { |
| 477 var name = type.name; |
| 478 |
| 479 // No name for the special type that contains top-level members. |
| 480 if (type.isTop) return ''; |
| 481 |
| 482 // Remove any type args or params that have been mangled into the name. |
| 483 var dollar = name.indexOf('\$', 0); |
| 484 if (dollar != -1) name = name.substring(0, dollar); |
| 485 |
| 486 return name; |
| 487 } |
| 488 |
| 489 /** Gets the anchor id for the document for [member]. */ |
| 490 memberAnchor(Member member) { |
| 491 return '${typeAnchor(member.declaringType)}-${member.name}'; |
| 492 } |
| 493 |
| 494 /** Writes a linked cross reference to [type]. */ |
| 495 typeReference(Type type) { |
| 496 // TODO(rnystrom): Do we need to handle ParameterTypes here like |
| 497 // annotation() does? |
| 498 return '<a href="${typeUrl(type)}" class="crossref">${typeName(type)}</a>'; |
| 430 } | 499 } |
| 431 | 500 |
| 432 /** | 501 /** |
| 433 * Creates a linked string for an optional type annotation. Returns an empty | 502 * Creates a linked string for an optional type annotation. Returns an empty |
| 434 * string if the type is Dynamic. | 503 * string if the type is Dynamic. |
| 435 */ | 504 */ |
| 436 optionalTypeRef(Type type) { | 505 annotation(Type enclosingType, Type type) { |
| 437 if (type.name == 'Dynamic') { | 506 if (type.name == 'Dynamic') return ''; |
| 438 return ''; | 507 |
| 439 } else { | 508 // If we're using a type parameter within the body of a generic class then |
| 440 return typeRef(type) + ' '; | 509 // just link back up to the class. |
| 510 if (type is ParameterType) { |
| 511 final library = sanitize(enclosingType.library.name); |
| 512 return '<a href="${typeUrl(enclosingType)}">${type.name}</a> '; |
| 441 } | 513 } |
| 514 |
| 515 // Link to the type. |
| 516 return '<a href="${typeUrl(type)}">${typeName(type)}</a> '; |
| 442 } | 517 } |
| 443 | 518 |
| 444 /** | 519 /** |
| 520 * This will be called whenever a doc comment hits a `[name]` in square |
| 521 * brackets. It will try to figure out what the name refers to and link or |
| 522 * style it appropriately. |
| 523 */ |
| 524 md.Node resolveNameReference(String name) { |
| 525 if (_currentMember != null) { |
| 526 // See if it's a parameter of the current method. |
| 527 for (final parameter in _currentMember.parameters) { |
| 528 if (parameter.name == name) { |
| 529 final element = new md.Element.text('span', name); |
| 530 element.attributes['class'] = 'param'; |
| 531 return element; |
| 532 } |
| 533 } |
| 534 } |
| 535 |
| 536 makeLink(String href) { |
| 537 final anchor = new md.Element.text('a', name); |
| 538 anchor.attributes['href'] = href; |
| 539 anchor.attributes['class'] = 'crossref'; |
| 540 return anchor; |
| 541 } |
| 542 |
| 543 // See if it's another member of the current type. |
| 544 if (_currentType != null) { |
| 545 var member = _currentType.members[name]; |
| 546 if (member != null) { |
| 547 // Special case: if the member we've resolved is a property (i.e. it wraps |
| 548 // a getter and/or setter then *that* member itself won't be on the docs, |
| 549 // just the getter or setter will be. So pick one of those to link to. |
| 550 if (member.isProperty) { |
| 551 if (member.canGet) { |
| 552 member = member.getter; |
| 553 } else { |
| 554 member = member.setter; |
| 555 } |
| 556 } |
| 557 |
| 558 return makeLink(memberUrl(member)); |
| 559 } |
| 560 } |
| 561 |
| 562 // See if it's another type in the current library. |
| 563 if (_currentLibrary != null) { |
| 564 final type = _currentLibrary.types[name]; |
| 565 if (type != null) { |
| 566 return makeLink(typeUrl(type)); |
| 567 } |
| 568 } |
| 569 |
| 570 // TODO(rnystrom): Should also consider: |
| 571 // * Names imported by libraries this library imports. |
| 572 // * Type parameters of the enclosing type. |
| 573 |
| 574 return new md.Element.text('code', name); |
| 575 } |
| 576 |
| 577 /** |
| 445 * Documents the code contained within [span]. Will include the previous | 578 * Documents the code contained within [span]. Will include the previous |
| 446 * Dartdoc associated with that span if found, and will include the syntax | 579 * Dartdoc associated with that span if found, and will include the syntax |
| 447 * highlighted code itself if desired. | 580 * highlighted code itself if desired. |
| 448 */ | 581 */ |
| 449 docCode(SourceSpan span, [bool showCode = false]) { | 582 docCode(SourceSpan span, [bool showCode = false]) { |
| 450 if (span == null) return; | 583 if (span == null) return; |
| 451 | 584 |
| 452 writeln('<div class="doc">'); | 585 writeln('<div class="doc">'); |
| 453 final comment = findComment(span); | 586 final comment = findComment(span); |
| 454 if (comment != null) { | 587 if (comment != null) { |
| 455 writeln('<p>$comment</p>'); | 588 writeln(md.markdownToHtml(comment)); |
| 456 } | 589 } |
| 457 | 590 |
| 458 if (showCode) { | 591 if (includeSource && showCode) { |
| 459 writeln('<pre class="source">'); | 592 writeln('<pre class="source">'); |
| 460 write(formatCode(span)); | 593 write(formatCode(span)); |
| 461 writeln('</pre>'); | 594 writeln('</pre>'); |
| 462 } | 595 } |
| 463 | 596 |
| 464 writeln('</div>'); | 597 writeln('</div>'); |
| 465 } | 598 } |
| 466 | 599 |
| 467 /** Finds the doc comment preceding the given source span, if there is one. */ | 600 /** Finds the doc comment preceding the given source span, if there is one. */ |
| 468 findComment(SourceSpan span) => findCommentInFile(span.file, span.start); | 601 findComment(SourceSpan span) => findCommentInFile(span.file, span.start); |
| (...skipping 15 matching lines...) Expand all Loading... |
| 484 | 617 |
| 485 while (true) { | 618 while (true) { |
| 486 final token = tokenizer.next(); | 619 final token = tokenizer.next(); |
| 487 if (token.kind == TokenKind.END_OF_FILE) break; | 620 if (token.kind == TokenKind.END_OF_FILE) break; |
| 488 | 621 |
| 489 if (token.kind == TokenKind.COMMENT) { | 622 if (token.kind == TokenKind.COMMENT) { |
| 490 final text = token.text; | 623 final text = token.text; |
| 491 if (text.startsWith('/**')) { | 624 if (text.startsWith('/**')) { |
| 492 // Remember that we've encountered a doc comment. | 625 // Remember that we've encountered a doc comment. |
| 493 lastComment = stripComment(token.text); | 626 lastComment = stripComment(token.text); |
| 627 } else if (text.startsWith('///')) { |
| 628 var line = text.substring(3, text.length); |
| 629 // Allow a leading space. |
| 630 if (line.startsWith(' ')) line = line.substring(1, text.length); |
| 631 if (lastComment == null) { |
| 632 lastComment = line; |
| 633 } else { |
| 634 lastComment = '$lastComment$line'; |
| 635 } |
| 494 } | 636 } |
| 495 } else if (token.kind == TokenKind.WHITESPACE) { | 637 } else if (token.kind == TokenKind.WHITESPACE) { |
| 496 // Ignore whitespace tokens. | 638 // Ignore whitespace tokens. |
| 497 } else if (token.kind == TokenKind.HASH) { | 639 } else if (token.kind == TokenKind.HASH) { |
| 498 // Look for #library() to find the library comment. | 640 // Look for #library() to find the library comment. |
| 499 final next = tokenizer.next(); | 641 final next = tokenizer.next(); |
| 500 if ((lastComment != null) && (next.kind == TokenKind.LIBRARY)) { | 642 if ((lastComment != null) && (next.kind == TokenKind.LIBRARY)) { |
| 501 comments[_libraryDoc] = lastComment; | 643 comments[_libraryDoc] = lastComment; |
| 502 lastComment = null; | 644 lastComment = null; |
| 503 } | 645 } |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 544 for (start = 0; start < Math.min(indentation, text.length); start++) { | 686 for (start = 0; start < Math.min(indentation, text.length); start++) { |
| 545 // Stop if we hit a non-whitespace character. | 687 // Stop if we hit a non-whitespace character. |
| 546 if (text[start] != ' ') break; | 688 if (text[start] != ' ') break; |
| 547 } | 689 } |
| 548 | 690 |
| 549 return text.substring(start); | 691 return text.substring(start); |
| 550 } | 692 } |
| 551 | 693 |
| 552 /** | 694 /** |
| 553 * Pulls the raw text out of a doc comment (i.e. removes the comment | 695 * Pulls the raw text out of a doc comment (i.e. removes the comment |
| 554 * characters. | 696 * characters). |
| 555 */ | 697 */ |
| 556 // TODO(rnystrom): Should handle [name] and [:code:] in comments. Should also | |
| 557 // break empty lines into multiple paragraphs. Other formatting? | |
| 558 // See dart/compiler/java/com/google/dart/compiler/backend/doc for ideas. | |
| 559 // (/DartDocumentationVisitor.java#180) | |
| 560 stripComment(comment) { | 698 stripComment(comment) { |
| 561 StringBuffer buf = new StringBuffer(); | 699 StringBuffer buf = new StringBuffer(); |
| 562 | 700 |
| 563 for (final line in comment.split('\n')) { | 701 for (final line in comment.split('\n')) { |
| 564 line = line.trim(); | 702 line = line.trim(); |
| 565 if (line.startsWith('/**')) line = line.substring(3, line.length); | 703 if (line.startsWith('/**')) line = line.substring(3, line.length); |
| 566 if (line.endsWith('*/')) line = line.substring(0, line.length-2); | 704 if (line.endsWith('*/')) line = line.substring(0, line.length - 2); |
| 567 line = line.trim(); | 705 line = line.trim(); |
| 568 while (line.startsWith('*')) line = line.substring(1, line.length); | 706 if (line.startsWith('* ')) { |
| 569 line = line.trim(); | 707 line = line.substring(2, line.length); |
| 708 } else if (line.startsWith('*')) { |
| 709 line = line.substring(1, line.length); |
| 710 } |
| 711 |
| 570 buf.add(line); | 712 buf.add(line); |
| 571 buf.add(' '); | 713 buf.add('\n'); |
| 572 } | 714 } |
| 573 | 715 |
| 574 return buf.toString(); | 716 return buf.toString(); |
| 575 } | 717 } |
| OLD | NEW |