Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(192)

Side by Side Diff: utils/dartdoc/dartdoc.dart

Issue 8725007: Lots of stuff hooking up markdown to dartdoc. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Respond to awesome reviews. Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « utils/dartdoc/dartdoc ('k') | utils/dartdoc/static/styles.css » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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}&lt;$params&gt;';
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}&lt;$args&gt;';
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
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
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 }
OLDNEW
« no previous file with comments | « utils/dartdoc/dartdoc ('k') | utils/dartdoc/static/styles.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698