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

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: Implement removeRange() on List in frog. 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
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 = 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}&lt;$params&gt;';
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}&lt;$args&gt;';
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698