Chromium Code Reviews| 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 = nameType(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); |
|
nweiz
2011/11/28 22:50:45
Unnecessary parens
Jennifer Messerly
2011/11/28 23:08:13
You don't want to show things that are extending o
Bob Nystrom
2011/11/29 02:44:08
Done.
Bob Nystrom
2011/11/29 02:44:08
I don't mind showing the full inheritance chain (i
| |
| 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 ${crossRefType(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 ${crossRefType(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 ${crossRefType(type.interfaces[0])} and |
| 281 ${typeRef(type.interfaces[1])}.'''); | 314 ${crossRefType(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('${crossRefType(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, namedConstructor: 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, |
| 317 [String namedConstructor = null]) { | 350 [String namedConstructor = null]) { |
|
nweiz
2011/11/28 22:50:45
It's confusing to me that namedConstructor is non-
Bob Nystrom
2011/11/29 02:44:08
Done.
| |
| 318 _totalMembers++; | 351 _totalMembers++; |
| 352 _currentMember = method; | |
| 319 | 353 |
| 320 writeln( | 354 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 | 355 |
| 326 // A null typeName means it's a top-level definition which is implicitly | 356 if (includeSource) { |
| 357 writeln('<span class="show-code">Code</span>'); | |
| 358 } | |
| 359 | |
| 360 // A null type name means it's a top-level definition which is implicitly | |
| 327 // static so doesn't need to annotate it. | 361 // static so doesn't need to annotate it. |
| 328 if (method.isStatic && (typeName != null)) { | 362 if (method.isStatic && (type.name != null)) { |
| 329 write('static '); | 363 write('static '); |
| 330 } | 364 } |
| 331 | 365 |
| 332 if (method.isConstructor) { | 366 if (method.isConstructor) { |
| 333 write(method.isConst ? 'const ' : 'new '); | 367 write(method.isConst ? 'const ' : 'new '); |
| 334 } | 368 } |
| 335 | 369 |
| 336 if (namedConstructor == null) { | 370 if (namedConstructor == null) { |
| 337 write(optionalTypeRef(method.returnType)); | 371 write(annotateType(type, method.returnType)); |
| 338 } | 372 } |
| 339 | 373 |
| 340 // Translate specially-named methods: getters, setters, operators. | 374 // Translate specially-named methods: getters, setters, operators. |
| 341 var name = method.name; | 375 var name = method.name; |
| 342 if (name.startsWith('get\$')) { | 376 if (name.startsWith('get\$')) { |
| 343 // Getter. | 377 // Getter. |
| 344 name = 'get ${name.substring(4)}'; | 378 name = 'get ${name.substring(4)}'; |
| 345 } else if (name.startsWith('set\$')) { | 379 } else if (name.startsWith('set\$')) { |
| 346 // Setter. | 380 // Setter. |
| 347 name = 'set ${name.substring(4)}'; | 381 name = 'set ${name.substring(4)}'; |
| 348 } else { | 382 } else { |
| 349 // See if it's an operator. | 383 // See if it's an operator. |
| 350 name = TokenKind.rawOperatorFromMethod(name); | 384 name = TokenKind.rawOperatorFromMethod(name); |
| 351 if (name == null) { | 385 if (name == null) { |
| 352 name = method.name; | 386 name = method.name; |
| 353 } else { | 387 } else { |
| 354 name = 'operator $name'; | 388 name = 'operator $name'; |
| 355 } | 389 } |
| 356 } | 390 } |
| 357 | 391 |
| 358 write('<strong>$name</strong>'); | 392 write('<strong>$name</strong>'); |
| 359 | 393 |
| 360 // Named constructors. | 394 // Named constructors. |
| 361 if (namedConstructor != null && namedConstructor != '') { | 395 if (namedConstructor != null && namedConstructor != '') { |
| 362 write('.'); | 396 write('.'); |
| 363 write(namedConstructor); | 397 write(namedConstructor); |
| 364 } | 398 } |
| 365 | 399 |
| 366 write('('); | 400 write('('); |
| 367 final paramList = []; | 401 final parameters = map(method.parameters, |
| 368 if (method.parameters == null) print(method.name); | 402 (p) => '${annotateType(type, p.type)}${p.name}'); |
| 369 for (final p in method.parameters) { | 403 write(Strings.join(parameters, ', ')); |
| 370 paramList.add('${optionalTypeRef(p.type)}${p.name}'); | |
| 371 } | |
| 372 write(Strings.join(paramList, ", ")); | |
| 373 write(')'); | 404 write(')'); |
| 374 | 405 |
| 375 write(''' <a class="anchor-link" href="#$typeName.${method.name}" | 406 write(''' <a class="anchor-link" href="#${memberUrl(method)}" |
| 376 title="Permalink to $typeName.$name">#</a>'''); | 407 title="Permalink to ${type.name}.$name">#</a>'''); |
| 377 writeln('</h4>'); | 408 writeln('</h4>'); |
| 378 | 409 |
| 379 docCode(method.span, showCode: true); | 410 docCode(method.span, showCode: true); |
| 380 | 411 |
| 381 writeln('</div>'); | 412 writeln('</div>'); |
| 382 } | 413 } |
| 383 | 414 |
| 384 /** Documents the field [field] in a type named [typeName]. */ | 415 /** Documents the field [field] of type [type]. */ |
| 385 docField(String typeName, FieldMember field) { | 416 docField(Type type, FieldMember field) { |
| 386 _totalMembers++; | 417 _totalMembers++; |
| 418 _currentMember = field; | |
| 387 | 419 |
| 388 writeln( | 420 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 | 421 |
| 394 // A null typeName means it's a top-level definition which is implicitly | 422 if (includeSource) { |
| 395 // static so doesn't need to annotate it. | 423 writeln('<span class="show-code">Code</span>'); |
| 396 if (field.isStatic && (typeName != null)) { | 424 } |
| 425 | |
| 426 // A null type name means it's a top-level definition which is implicitly | |
| 427 // static so don't need to annotate it. | |
| 428 if (field.isStatic && (type.name != null)) { | |
|
Jennifer Messerly
2011/11/28 23:08:13
!type.isTop ? That'd be more self documenting. I w
Bob Nystrom
2011/11/29 02:44:08
Oh, is that what isTop means? Done.
| |
| 397 write('static '); | 429 write('static '); |
| 398 } | 430 } |
| 399 | 431 |
| 400 if (field.isFinal) { | 432 if (field.isFinal) { |
| 401 write('final '); | 433 write('final '); |
| 402 } else if (field.type.name == 'Dynamic') { | 434 } else if (field.type.name == 'Dynamic') { |
| 403 write('var '); | 435 write('var '); |
| 404 } | 436 } |
| 405 | 437 |
| 406 write(optionalTypeRef(field.type)); | 438 write(annotateType(type, field.type)); |
| 407 write( | 439 write( |
| 408 ''' | 440 ''' |
| 409 <strong>${field.name}</strong> <a class="anchor-link" | 441 <strong>${field.name}</strong> <a class="anchor-link" |
| 410 href="#$typeName.${field.name}" | 442 href="#${memberUrl(field)}" |
| 411 title="Permalink to $typeName.${field.name}">#</a> | 443 title="Permalink to ${type.name}.${field.name}">#</a> |
| 412 </h4> | 444 </h4> |
| 413 '''); | 445 '''); |
| 414 | 446 |
| 415 docCode(field.span, showCode: true); | 447 docCode(field.span, showCode: true); |
| 416 writeln('</div>'); | 448 writeln('</div>'); |
| 417 } | 449 } |
| 418 | 450 |
| 419 /** | 451 /** Generates a human-friendly string representation for a type. */ |
| 420 * Writes a type annotation for [type]. Will hyperlink it to that type's | 452 nameType(Type type) { |
|
nweiz
2011/11/28 22:50:45
I think "typeName" is a better name for this... I
Bob Nystrom
2011/11/29 02:44:08
Done.
| |
| 421 * documentation if possible. | 453 // See if it's a generic type. |
| 422 */ | 454 if (type.isGeneric) { |
| 423 typeRef(Type type) { | 455 final typeParams = type.genericType.typeParameters; |
|
nweiz
2011/11/28 22:50:45
If type.isGeneric is true, type.genericType.typePa
Bob Nystrom
2011/11/29 02:44:08
Right, but Type doesn't define typeParameters so i
| |
| 424 if (type.library != null) { | 456 final params = Strings.join(map(typeParams, (p) => p.name), ', '); |
| 425 final library = sanitize(type.library.name); | 457 return '${type.name}<$params>'; |
| 426 return '<a href="${library}.html#${type.name}">${type.name}</a>'; | |
| 427 } else { | |
| 428 return type.name; | |
| 429 } | 458 } |
| 459 | |
| 460 // See if it's an instantiation of a generic type. | |
| 461 final typeArgs = type.typeArgsInOrder; | |
| 462 if (typeArgs != null) { | |
| 463 final args = Strings.join(map(typeArgs, (arg) => arg.name), ', '); | |
|
nweiz
2011/11/28 22:50:45
Shouldn't this use nameType(arg) or crossRefType(a
Bob Nystrom
2011/11/29 02:44:08
Good catch. Forgot about nested types. Done.
| |
| 464 return '${type.genericType.name}<$args>'; | |
| 465 } | |
| 466 | |
| 467 // Regular type. | |
| 468 return type.name; | |
| 469 } | |
| 470 | |
| 471 /** Gets the URL to the documentation for [library]. */ | |
| 472 libraryUrl(Library library) { | |
|
nweiz
2011/11/28 22:50:45
Seems like this should be a one-liner.
Jennifer Messerly
2011/11/28 23:08:13
this can be a one liner:
libraryUrl(Library librar
Bob Nystrom
2011/11/29 02:44:08
Done.
| |
| 473 final libName = sanitize(library.name); | |
| 474 return '$libName.html'; | |
| 475 } | |
| 476 | |
| 477 /** Gets the URL for the documentation for [type]. */ | |
| 478 typeUrl(Type type) { | |
| 479 final library = sanitize(type.library.name); | |
|
nweiz
2011/11/28 22:50:45
Unused variable.
Bob Nystrom
2011/11/29 02:44:08
Done.
| |
| 480 return '${libraryUrl(type.library)}#${typeAnchor(type)}'; | |
| 481 } | |
| 482 | |
| 483 /** Gets the URL for the documentation for [member]. */ | |
| 484 memberUrl(Member member) { | |
|
nweiz
2011/11/28 22:50:45
=>
Bob Nystrom
2011/11/29 02:44:08
Done.
| |
| 485 return '${typeUrl(member.declaringType)}.${member.name}'; | |
|
nweiz
2011/11/28 22:50:45
Shouldn't this be '#${memberAnchor(member)}'?
Bob Nystrom
2011/11/29 02:44:08
It's a little hairy but typeUrl already includes p
| |
| 486 } | |
| 487 | |
| 488 /** Gets the anchor id for the document for [type]. */ | |
| 489 typeAnchor(Type type) { | |
| 490 var name = type.name; | |
| 491 | |
| 492 // No name for the special type that contains top-level members. | |
| 493 if (name == null) return ''; | |
|
nweiz
2011/11/28 22:50:45
Why not return "_top" or something like that?
Jennifer Messerly
2011/11/28 23:08:13
I'd use type.isTop here too (and anywhere else :)
Bob Nystrom
2011/11/29 02:44:08
That could be an actual type name. An empty string
nweiz
2011/11/29 19:57:19
But an empty string won't actually provide an anch
| |
| 494 | |
| 495 // Remove any type args or params that have been mangled into the name. | |
|
nweiz
2011/11/28 22:50:45
How sure are we that this can't produce duplicate
Bob Nystrom
2011/11/29 02:44:08
I'm not totally sure. I think the "$" are only use
| |
| 496 var dollar = name.indexOf('\$', 0); | |
| 497 if (dollar != -1) name = name.substring(0, dollar); | |
| 498 | |
| 499 return name; | |
| 500 } | |
| 501 | |
| 502 /** Gets the anchor id for the document for [member]. */ | |
| 503 memberAnchor(Member member) { | |
| 504 return '${typeAnchor(member.declaringType)}.${member.name}'; | |
|
nweiz
2011/11/28 22:50:45
I don't like periods in ids. Yes, it's allowed by
Bob Nystrom
2011/11/29 02:44:08
Done.
| |
| 505 } | |
| 506 | |
| 507 /** Writes a linked cross reference to [type]. */ | |
| 508 crossRefType(Type type) { | |
|
nweiz
2011/11/28 22:50:45
typeReference
Bob Nystrom
2011/11/29 02:44:08
Done.
| |
| 509 // TODO(rnystrom): Do we need to handle ParameterTypes here like | |
| 510 // annotateType() does? | |
| 511 final library = sanitize(type.library.name); | |
|
nweiz
2011/11/28 22:50:45
Unused variable
Bob Nystrom
2011/11/29 02:44:08
Done.
| |
| 512 final name = nameType(type); | |
| 513 return '<a href="${typeUrl(type)}" class="crossref">$name</a>'; | |
| 430 } | 514 } |
| 431 | 515 |
| 432 /** | 516 /** |
| 433 * Creates a linked string for an optional type annotation. Returns an empty | 517 * Creates a linked string for an optional type annotation. Returns an empty |
| 434 * string if the type is Dynamic. | 518 * string if the type is Dynamic. |
| 435 */ | 519 */ |
| 436 optionalTypeRef(Type type) { | 520 annotateType(Type enclosingType, Type type) { |
|
nweiz
2011/11/28 22:50:45
typeAnnotation
Bob Nystrom
2011/11/29 02:44:08
Did "annotation" instead.
| |
| 437 if (type.name == 'Dynamic') { | 521 if (type.name == 'Dynamic') return ''; |
| 438 return ''; | 522 |
| 439 } else { | 523 // If we're using a type parameter within the body of a generic class then |
| 440 return typeRef(type) + ' '; | 524 // just link back up to the class. |
| 525 if (type is ParameterType) { | |
| 526 final library = sanitize(enclosingType.library.name); | |
| 527 return '<a href="${typeUrl(enclosingType)}">${type.name}</a> '; | |
| 441 } | 528 } |
| 529 | |
| 530 // Link to the type. | |
| 531 return '<a href="${typeUrl(type)}">${nameType(type)}</a> '; | |
| 442 } | 532 } |
| 443 | 533 |
| 444 /** | 534 /** |
| 535 * This will be called whenever a doc comment hits a `[name]` in square | |
| 536 * brackets. It will try to figure out what the name refers to and link or | |
| 537 * style it appropriately. | |
| 538 */ | |
| 539 md.Node resolveNameReference(String name) { | |
|
Jennifer Messerly
2011/11/28 23:08:13
This reminds me how much I dislike prefixes. At le
Bob Nystrom
2011/11/29 02:44:08
Yeah. Needed it to distinguish between frog and ma
| |
| 540 if (_currentMember != null) { | |
| 541 // See if it's a parameter of the current method. | |
| 542 for (final parameter in _currentMember.parameters) { | |
| 543 if (parameter.name == name) { | |
| 544 final element = new md.Element.text('span', name); | |
| 545 element.attributes['class'] = 'param'; | |
| 546 return element; | |
| 547 } | |
| 548 } | |
| 549 } | |
| 550 | |
| 551 makeLink(String href) { | |
| 552 final anchor = new md.Element.text('a', name); | |
| 553 anchor.attributes['href'] = href; | |
| 554 anchor.attributes['class'] = 'crossref'; | |
| 555 return anchor; | |
| 556 } | |
| 557 | |
| 558 // See if it's another member of the current type. | |
| 559 if (_currentType != null) { | |
| 560 final member = _currentType.members[name]; | |
| 561 if (member != null) { | |
|
nweiz
2011/11/28 22:50:45
if (member == null) continue;
Indentation is bad
Bob Nystrom
2011/11/29 02:44:08
Continue in an if? ;)
nweiz
2011/11/29 19:57:19
Oh ha, I'm good at reading code.
| |
| 562 // Special case: if the member we've resolved is a property (i.e. it wraps | |
| 563 // a getter and/or setter then *that* member itself won't be on the docs, | |
| 564 // just the getter or setter will be. So pick one of those to link to. | |
| 565 var memberName = member.name; | |
| 566 if (member.isProperty) { | |
| 567 if (member.canGet) { | |
| 568 memberName = member.getter.name; | |
| 569 } else { | |
| 570 memberName = member.setter.name; | |
| 571 } | |
| 572 } | |
|
nweiz
2011/11/28 22:50:45
Shouldn't this stuff be in memberAnchor?
Bob Nystrom
2011/11/29 02:44:08
This goes in the other direction. memberAnchor wil
| |
| 573 | |
| 574 return makeLink('#${_currentType.name}.$memberName'); | |
|
nweiz
2011/11/28 22:50:45
memberAnchor(member)
Bob Nystrom
2011/11/29 02:44:08
Done.
| |
| 575 } | |
| 576 } | |
| 577 | |
| 578 // See if it's another type in the current library. | |
| 579 if (_currentLibrary != null) { | |
| 580 final type = _currentLibrary.types[name]; | |
| 581 if (type != null) { | |
| 582 return makeLink('#${type.name}'); | |
|
nweiz
2011/11/28 22:50:45
typeAnchor(type)
Bob Nystrom
2011/11/29 02:44:08
Done.
| |
| 583 } | |
| 584 } | |
| 585 | |
| 586 // TODO(rnystrom): Should also consider: | |
| 587 // * Names imported by libraries this library imports. | |
| 588 // * Type parameters of the enclosing type. | |
| 589 | |
| 590 return new md.Element.text('code', name); | |
|
nweiz
2011/11/28 22:50:45
At some point, this should probably warn to protec
Bob Nystrom
2011/11/29 02:44:08
Yeah, I'm not sure what the best path for this is.
nweiz
2011/11/29 19:57:19
It seems like you want it to be a warning to mitig
| |
| 591 } | |
| 592 | |
| 593 /** | |
| 445 * Documents the code contained within [span]. Will include the previous | 594 * Documents the code contained within [span]. Will include the previous |
| 446 * Dartdoc associated with that span if found, and will include the syntax | 595 * Dartdoc associated with that span if found, and will include the syntax |
| 447 * highlighted code itself if desired. | 596 * highlighted code itself if desired. |
| 448 */ | 597 */ |
| 449 docCode(SourceSpan span, [bool showCode = false]) { | 598 docCode(SourceSpan span, [bool showCode = false]) { |
| 450 if (span == null) return; | 599 if (span == null) return; |
| 451 | 600 |
| 452 writeln('<div class="doc">'); | 601 writeln('<div class="doc">'); |
| 453 final comment = findComment(span); | 602 final comment = findComment(span); |
| 454 if (comment != null) { | 603 if (comment != null) { |
| 455 writeln('<p>$comment</p>'); | 604 final html = md.markdownToHtml(comment); |
| 605 writeln(html); | |
|
Jennifer Messerly
2011/11/28 23:08:13
as one line? writeln(md.markdownToHtml(comment));
Bob Nystrom
2011/11/29 02:44:08
Done.
| |
| 456 } | 606 } |
| 457 | 607 |
| 458 if (showCode) { | 608 if (includeSource && showCode) { |
| 459 writeln('<pre class="source">'); | 609 writeln('<pre class="source">'); |
| 460 write(formatCode(span)); | 610 write(formatCode(span)); |
| 461 writeln('</pre>'); | 611 writeln('</pre>'); |
| 462 } | 612 } |
| 463 | 613 |
| 464 writeln('</div>'); | 614 writeln('</div>'); |
| 465 } | 615 } |
| 466 | 616 |
| 467 /** Finds the doc comment preceding the given source span, if there is one. */ | 617 /** Finds the doc comment preceding the given source span, if there is one. */ |
| 468 findComment(SourceSpan span) => findCommentInFile(span.file, span.start); | 618 findComment(SourceSpan span) => findCommentInFile(span.file, span.start); |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 484 | 634 |
| 485 while (true) { | 635 while (true) { |
| 486 final token = tokenizer.next(); | 636 final token = tokenizer.next(); |
| 487 if (token.kind == TokenKind.END_OF_FILE) break; | 637 if (token.kind == TokenKind.END_OF_FILE) break; |
| 488 | 638 |
| 489 if (token.kind == TokenKind.COMMENT) { | 639 if (token.kind == TokenKind.COMMENT) { |
| 490 final text = token.text; | 640 final text = token.text; |
| 491 if (text.startsWith('/**')) { | 641 if (text.startsWith('/**')) { |
| 492 // Remember that we've encountered a doc comment. | 642 // Remember that we've encountered a doc comment. |
| 493 lastComment = stripComment(token.text); | 643 lastComment = stripComment(token.text); |
| 644 } else if (text.startsWith('///')) { | |
| 645 var line = text.substring(3, text.length); | |
| 646 // Allow a leading space. | |
| 647 if (line.startsWith(' ')) line = line.substring(1, text.length); | |
| 648 if (lastComment == null) { | |
| 649 lastComment = line; | |
| 650 } else { | |
| 651 lastComment = '$lastComment$line'; | |
| 652 } | |
| 494 } | 653 } |
| 495 } else if (token.kind == TokenKind.WHITESPACE) { | 654 } else if (token.kind == TokenKind.WHITESPACE) { |
| 496 // Ignore whitespace tokens. | 655 // Ignore whitespace tokens. |
| 497 } else if (token.kind == TokenKind.HASH) { | 656 } else if (token.kind == TokenKind.HASH) { |
| 498 // Look for #library() to find the library comment. | 657 // Look for #library() to find the library comment. |
| 499 final next = tokenizer.next(); | 658 final next = tokenizer.next(); |
| 500 if ((lastComment != null) && (next.kind == TokenKind.LIBRARY)) { | 659 if ((lastComment != null) && (next.kind == TokenKind.LIBRARY)) { |
| 501 comments[_libraryDoc] = lastComment; | 660 comments[_libraryDoc] = lastComment; |
| 502 lastComment = null; | 661 lastComment = null; |
| 503 } | 662 } |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 544 for (start = 0; start < Math.min(indentation, text.length); start++) { | 703 for (start = 0; start < Math.min(indentation, text.length); start++) { |
| 545 // Stop if we hit a non-whitespace character. | 704 // Stop if we hit a non-whitespace character. |
| 546 if (text[start] != ' ') break; | 705 if (text[start] != ' ') break; |
| 547 } | 706 } |
| 548 | 707 |
| 549 return text.substring(start); | 708 return text.substring(start); |
| 550 } | 709 } |
| 551 | 710 |
| 552 /** | 711 /** |
| 553 * Pulls the raw text out of a doc comment (i.e. removes the comment | 712 * Pulls the raw text out of a doc comment (i.e. removes the comment |
| 554 * characters. | 713 * characters). |
| 555 */ | 714 */ |
| 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) { | 715 stripComment(comment) { |
| 561 StringBuffer buf = new StringBuffer(); | 716 StringBuffer buf = new StringBuffer(); |
| 562 | 717 |
| 563 for (final line in comment.split('\n')) { | 718 for (final line in comment.split('\n')) { |
| 564 line = line.trim(); | 719 line = line.trim(); |
| 565 if (line.startsWith('/**')) line = line.substring(3, line.length); | 720 if (line.startsWith('/**')) line = line.substring(3, line.length); |
| 566 if (line.endsWith('*/')) line = line.substring(0, line.length-2); | 721 if (line.endsWith('*/')) line = line.substring(0, line.length - 2); |
| 567 line = line.trim(); | 722 line = line.trim(); |
| 568 while (line.startsWith('*')) line = line.substring(1, line.length); | 723 if (line.startsWith('* ')) { |
| 569 line = line.trim(); | 724 line = line.substring(2, line.length); |
| 725 } else if (line.startsWith('*')) { | |
| 726 line = line.substring(1, line.length); | |
| 727 } | |
| 728 | |
| 570 buf.add(line); | 729 buf.add(line); |
| 571 buf.add(' '); | 730 buf.add('\n'); |
| 572 } | 731 } |
| 573 | 732 |
| 574 return buf.toString(); | 733 return buf.toString(); |
| 575 } | 734 } |
| OLD | NEW |