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 |