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

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

Issue 8947005: Refactor dartdoc into a class. Use method overriding to extend. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: 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 /** 5 /**
6 * To use it, from this directory, run: 6 * To use it, from this directory, run:
7 * 7 *
8 * $ dartdoc <path to .dart file> 8 * $ dartdoc <path to .dart file>
9 * 9 *
10 * This will create a "docs" directory with the docs for your libraries. To 10 * This will create a "docs" directory with the docs for your libraries. To
11 * create these beautiful docs, dartdoc parses your library and every library 11 * create these beautiful docs, dartdoc parses your library and every library
12 * it imports (recursively). From each library, it parses all classes and 12 * it imports (recursively). From each library, it parses all classes and
13 * members, finds the associated doc comments and builds crosslinked docs from 13 * members, finds the associated doc comments and builds crosslinked docs from
14 * them. 14 * them.
15 */ 15 */
16 #library('dartdoc'); 16 #library('dartdoc');
17 17
18 #import('../../frog/lang.dart'); 18 #import('../../frog/lang.dart');
19 #import('../../frog/file_system.dart'); 19 #import('../../frog/file_system.dart');
20 #import('../../frog/file_system_node.dart'); 20 #import('../../frog/file_system_node.dart');
21 #import('../markdown/lib.dart', prefix: 'md'); 21 #import('../markdown/lib.dart', prefix: 'md');
22 22
23 #source('classify.dart'); 23 #source('classify.dart');
24 #source('comment_map.dart');
24 #source('files.dart'); 25 #source('files.dart');
25 #source('utils.dart'); 26 #source('utils.dart');
26 27
27 /** Path to corePath library. */ 28 /** Path to corePath library. */
28 final corePath = 'lib'; 29 final corePath = 'lib';
29 30
30 /** Path to generate html files into. */ 31 /** Path to generate html files into. */
31 final outdir = 'docs'; 32 final outdir = 'docs';
32 33
33 /** Set to `false` to not include the source code in the generated docs. */
34 bool includeSource = true;
35
36 FileSystem files; 34 FileSystem files;
37 35
38 /** Special comment position used to store the library-level doc comment. */
39 final _libraryDoc = -1;
40
41 /** The library that we're currently generating docs for. */
42 Library _currentLibrary;
43
44 /** The type that we're currently generating docs for. */
45 Type _currentType;
46
47 /** The member that we're currently generating docs for. */
48 Member _currentMember;
49
50 /**
51 * The cached lookup-table to associate doc comments with spans. The outer map
52 * is from filenames to doc comments in that file. The inner map maps from the
53 * token positions to doc comments. Each position is the starting offset of the
54 * next non-comment token *following* the doc comment. For example, the position
55 * for this comment would be the position of the "Map" token below.
56 */
57 Map<String, Map<int, String>> _comments;
58
59 /** A callback that returns additional Markdown documentation for a type. */
60 typedef String TypeDocumenter(Type type);
61
62 /** A list of callbacks registered for documenting types. */
63 List<TypeDocumenter> _typeDocumenters;
64
65 /** A callback that returns additional Markdown documentation for a method. */
66 typedef String MethodDocumenter(MethodMember method);
67
68 /** A list of callbacks registered for documenting methods. */
69 List<MethodDocumenter> _methodDocumenters;
70
71 /** A callback that returns additional Markdown documentation for a field. */
72 typedef String FieldDocumenter(FieldMember field);
73
74 /** A list of callbacks registered for documenting fields. */
75 List<FieldDocumenter> _fieldDocumenters;
76
77 int _totalLibraries = 0;
78 int _totalTypes = 0;
79 int _totalMembers = 0;
80
81 /** 36 /**
82 * Run this from the `utils/dartdoc` directory. 37 * Run this from the `utils/dartdoc` directory.
83 */ 38 */
84 void main() { 39 void main() {
85 // The entrypoint of the library to generate docs for. 40 // The entrypoint of the library to generate docs for.
86 final entrypoint = process.argv[2]; 41 final entrypoint = process.argv[process.argv.length - 1];
87 42
88 // Parse the dartdoc options. 43 // Parse the dartdoc options.
89 for (int i = 3; i < process.argv.length; i++) { 44 bool includeSource = true;
45
46 for (int i = 2; i < process.argv.length - 1; i++) {
90 final arg = process.argv[i]; 47 final arg = process.argv[i];
91 switch (arg) { 48 switch (arg) {
92 case '--no-code': 49 case '--no-code':
93 includeSource = false; 50 includeSource = false;
94 break; 51 break;
95 52
96 default: 53 default:
97 print('Unknown option: $arg'); 54 print('Unknown option: $arg');
98 } 55 }
99 } 56 }
100 57
101 files = new NodeFileSystem(); 58 files = new NodeFileSystem();
102 parseOptions('../../frog', [] /* args */, files); 59 parseOptions('../../frog', [] /* args */, files);
103 initializeWorld(files); 60 initializeWorld(files);
104 61
62 var dartdoc;
105 final elapsed = time(() { 63 final elapsed = time(() {
106 initializeDartDoc(); 64 dartdoc = new Dartdoc();
107 document(entrypoint); 65 dartdoc.includeSource = includeSource;
66 dartdoc.document(entrypoint);
108 }); 67 });
109 68
110 printStats(elapsed); 69 print('Documented ${dartdoc._totalLibraries} libraries, ' +
70 '${dartdoc._totalTypes} types, and ' +
71 '${dartdoc._totalMembers} members in ${elapsed}msec.');
111 } 72 }
112 73
113 void initializeDartDoc() { 74 class Dartdoc {
114 _comments = <Map<int, String>>{}; 75 /** Set to `false` to not include the source code in the generated docs. */
115 _typeDocumenters = <TypeDocumenter>[]; 76 bool includeSource = true;
116 _methodDocumenters = <MethodDocumenter>[]; 77
117 _fieldDocumenters = <FieldDocumenter>[]; 78 /**
118 79 * The cached lookup-table to associate doc comments with spans. The outer map
119 // Patch in support for [:...:]-style code to the markdown parser. 80 * is from filenames to doc comments in that file. The inner map maps from the
120 // TODO(rnystrom): Markdown already has syntax for this. Phase this out? 81 * token positions to doc comments. Each position is the starting offset of
121 md.InlineParser.syntaxes.insertRange(0, 1, 82 * the next non-comment token *following* the doc comment. For example, the
122 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); 83 * position for this comment would be the position of the "Map" token below.
123 84 */
124 md.setImplicitLinkResolver(resolveNameReference); 85 CommentMap _comments;
nweiz 2011/12/15 19:39:04 This long doc comment seems redundant with the Com
Bob Nystrom 2011/12/15 20:20:11 Done.
125 } 86
126 87 /** The library that we're currently generating docs for. */
127 document(String entrypoint) { 88 Library _currentLibrary;
128 try { 89
129 var oldDietParse = options.dietParse; 90 /** The type that we're currently generating docs for. */
130 options.dietParse = true; 91 Type _currentType;
131 92
132 // Handle the built-in entrypoints. 93 /** The member that we're currently generating docs for. */
133 switch (entrypoint) { 94 Member _currentMember;
134 case 'corelib': 95
135 world.getOrAddLibrary('dart:core'); 96 int _totalLibraries = 0;
136 world.getOrAddLibrary('dart:coreimpl'); 97 int _totalTypes = 0;
137 world.getOrAddLibrary('dart:json'); 98 int _totalMembers = 0;
138 world.process(); 99
139 break; 100 Dartdoc()
140 101 : _comments = new CommentMap() {
nweiz 2011/12/15 19:39:04 I thought initializer lists were supposed to be on
Bob Nystrom 2011/12/15 20:20:11 I don't think we specify one way or the other. I t
141 case 'dom': 102 // Patch in support for [:...:]-style code to the markdown parser.
142 world.getOrAddLibrary('dart:core'); 103 // TODO(rnystrom): Markdown already has syntax for this. Phase this out?
143 world.getOrAddLibrary('dart:coreimpl'); 104 md.InlineParser.syntaxes.insertRange(0, 1,
144 world.getOrAddLibrary('dart:json'); 105 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]'));
nweiz 2011/12/15 19:39:04 Won't this add a new syntax entry to the markdown
Bob Nystrom 2011/12/15 20:20:11 Hmm, yes. It shouldn't cause any problems but it i
145 world.getOrAddLibrary('dart:dom'); 106
146 world.process(); 107 md.setImplicitLinkResolver(resolveNameReference);
nweiz 2011/12/15 19:39:04 This also seems potentially troublesome if someone
Bob Nystrom 2011/12/15 20:20:11 Yeah... :(
147 break; 108 }
148 109
149 case 'html': 110 document(String entrypoint) {
150 world.getOrAddLibrary('dart:core'); 111 try {
151 world.getOrAddLibrary('dart:coreimpl'); 112 var oldDietParse = options.dietParse;
152 world.getOrAddLibrary('dart:json'); 113 options.dietParse = true;
153 world.getOrAddLibrary('dart:dom'); 114
154 world.getOrAddLibrary('dart:html'); 115 // Handle the built-in entrypoints.
155 world.process(); 116 switch (entrypoint) {
156 break; 117 case 'corelib':
157 118 world.getOrAddLibrary('dart:core');
158 default: 119 world.getOrAddLibrary('dart:coreimpl');
159 // Normal entrypoint script. 120 world.getOrAddLibrary('dart:json');
160 world.processDartScript(entrypoint); 121 world.process();
161 } 122 break;
162 123
163 world.resolveAll(); 124 case 'dom':
164 125 world.getOrAddLibrary('dart:core');
165 // Generate the docs. 126 world.getOrAddLibrary('dart:coreimpl');
166 docIndex(); 127 world.getOrAddLibrary('dart:json');
167 for (final library in world.libraries.getValues()) { 128 world.getOrAddLibrary('dart:dom');
168 docLibrary(library); 129 world.process();
169 } 130 break;
170 } finally { 131
171 options.dietParse = oldDietParse; 132 case 'html':
172 } 133 world.getOrAddLibrary('dart:core');
173 } 134 world.getOrAddLibrary('dart:coreimpl');
174 135 world.getOrAddLibrary('dart:json');
175 printStats(num elapsed) { 136 world.getOrAddLibrary('dart:dom');
176 print('Documented $_totalLibraries libraries, $_totalTypes types, and ' + 137 world.getOrAddLibrary('dart:html');
177 '$_totalMembers members in ${elapsed}msec.'); 138 world.process();
178 } 139 break;
179 140
180 writeHeader(String title) { 141 default:
181 writeln( 142 // Normal entrypoint script.
182 ''' 143 world.processDartScript(entrypoint);
183 <!DOCTYPE html> 144 }
184 <html> 145
185 <head> 146 world.resolveAll();
186 <meta charset="utf-8"> 147
187 <title>$title</title> 148 // Generate the docs.
188 <link rel="stylesheet" type="text/css" 149 docIndex();
189 href="${relativePath('styles.css')}" /> 150 for (final library in world.libraries.getValues()) {
190 <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700,8 00" rel="stylesheet" type="text/css"> 151 docLibrary(library);
191 <script src="${relativePath('interact.js')}"></script> 152 }
192 </head> 153 } finally {
193 <body> 154 options.dietParse = oldDietParse;
194 <div class="page"> 155 }
195 '''); 156 }
196 docNavigation(); 157
197 writeln('<div class="content">'); 158 writeHeader(String title) {
198 }
199
200 writeFooter() {
201 writeln(
202 '''
203 </div>
204 <div class="footer"</div>
205 </body></html>
206 ''');
207 }
208
209 docIndex() {
210 startFile('index.html');
211
212 writeHeader('Dart Documentation');
213
214 writeln('<h1>Dart Documentation</h1>');
215 writeln('<h3>Libraries</h3>');
216
217 for (final library in orderByName(world.libraries)) {
218 writeln( 159 writeln(
219 ''' 160 '''
220 <h4>${a(libraryUrl(library), library.name)}</h4> 161 <!DOCTYPE html>
162 <html>
163 <head>
164 <meta charset="utf-8">
165 <title>$title</title>
166 <link rel="stylesheet" type="text/css"
167 href="${relativePath('styles.css')}" />
168 <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700 ,800" rel="stylesheet" type="text/css">
169 <script src="${relativePath('interact.js')}"></script>
170 </head>
171 <body>
172 <div class="page">
221 '''); 173 ''');
222 } 174 docNavigation();
223 175 writeln('<div class="content">');
224 writeFooter(); 176 }
225 endFile(); 177
226 } 178 writeFooter() {
227
228 docNavigation() {
229 writeln(
230 '''
231 <div class="nav">
232 <h1>${a("index.html", "Dart Documentation")}</h1>
233 ''');
234
235 for (final library in orderByName(world.libraries)) {
236 write('<h2><div class="icon-library"></div>');
237
238 if ((_currentLibrary == library) && (_currentType == null)) {
239 write('<strong>${library.name}</strong>');
240 } else {
241 write('${a(libraryUrl(library), library.name)}');
242 }
243 write('</h2>');
244
245 // Only expand classes in navigation for current library.
246 if (_currentLibrary == library) docLibraryNavigation(library);
247 }
248
249 writeln('</div>');
250 }
251
252 /** Writes the navigation for the types contained by the given library. */
253 docLibraryNavigation(Library library) {
254 // Show the exception types separately.
255 final types = <Type>[];
256 final exceptions = <Type>[];
257
258 for (final type in orderByName(library.types)) {
259 if (type.isTop) continue;
260 if (type.name.startsWith('_')) continue;
261
262 if (type.name.endsWith('Exception')) {
263 exceptions.add(type);
264 } else {
265 types.add(type);
266 }
267 }
268
269 if ((types.length == 0) && (exceptions.length == 0)) return;
270
271 writeType(String icon, Type type) {
272 write('<li>');
273 if (_currentType == type) {
274 write(
275 '<div class="icon-$icon"></div><strong>${typeName(type)}</strong>');
276 } else {
277 write(a(typeUrl(type),
278 '<div class="icon-$icon"></div>${typeName(type)}'));
279 }
280 writeln('</li>');
281 }
282
283 writeln('<ul>');
284 types.forEach((type) => writeType(type.isClass ? 'class' : 'interface',
285 type));
286 exceptions.forEach((type) => writeType('exception', type));
287 writeln('</ul>');
288 }
289
290 String _runDocumenters(var item, List<Function> documenters) =>
291 Strings.join(map(documenters, (doc) => doc(item)), '\n\n');
292
293 docLibrary(Library library) {
294 _totalLibraries++;
295 _currentLibrary = library;
296 _currentType = null;
297
298 startFile(libraryUrl(library));
299 writeHeader(library.name);
300 writeln('<h1>Library <strong>${library.name}</strong></h1>');
301
302 // Look for a comment for the entire library.
303 final comment = findCommentInFile(library.baseSource, _libraryDoc);
304 if (comment != null) {
305 final html = md.markdownToHtml(comment);
306 writeln('<div class="doc">$html</div>');
307 }
308
309 // Document the top-level members.
310 docMembers(library.topType);
311
312 // Document the types.
313 final classes = <Type>[];
314 final interfaces = <Type>[];
315 final exceptions = <Type>[];
316
317 for (final type in orderByName(library.types)) {
318 if (type.isTop) continue;
319 if (type.name.startsWith('_')) continue;
320
321 if (type.name.endsWith('Exception')) {
322 exceptions.add(type);
323 } else if (type.isClass) {
324 classes.add(type);
325 } else {
326 interfaces.add(type);
327 }
328 }
329
330 docTypes(classes, 'Classes');
331 docTypes(interfaces, 'Interfaces');
332 docTypes(exceptions, 'Exceptions');
333
334 writeFooter();
335 endFile();
336
337 for (final type in library.types.getValues()) {
338 if (!type.isTop) docType(type);
339 }
340 }
341
342 docTypes(List<Type> types, String header) {
343 if (types.length == 0) return;
344
345 writeln('<h3>$header</h3>');
346
347 for (final type in types) {
348 writeln( 179 writeln(
349 ''' 180 '''
350 <div class="type"> 181 </div>
351 <h4> 182 <div class="footer"</div>
352 ${a(typeUrl(type), "<strong>${typeName(type)}</strong>")} 183 </body></html>
184 ''');
185 }
186
187 docIndex() {
188 startFile('index.html');
189
190 writeHeader('Dart Documentation');
191
192 writeln('<h1>Dart Documentation</h1>');
193 writeln('<h3>Libraries</h3>');
194
195 for (final library in orderByName(world.libraries)) {
196 writeln(
197 '''
198 <h4>${a(libraryUrl(library), library.name)}</h4>
199 ''');
200 }
201
202 writeFooter();
203 endFile();
204 }
205
206 docNavigation() {
207 writeln(
208 '''
209 <div class="nav">
210 <h1>${a("index.html", "Dart Documentation")}</h1>
211 ''');
212
213 for (final library in orderByName(world.libraries)) {
214 write('<h2><div class="icon-library"></div>');
215
216 if ((_currentLibrary == library) && (_currentType == null)) {
217 write('<strong>${library.name}</strong>');
218 } else {
219 write('${a(libraryUrl(library), library.name)}');
220 }
221 write('</h2>');
222
223 // Only expand classes in navigation for current library.
224 if (_currentLibrary == library) docLibraryNavigation(library);
225 }
226
227 writeln('</div>');
228 }
229
230 /** Writes the navigation for the types contained by the given library. */
231 docLibraryNavigation(Library library) {
232 // Show the exception types separately.
233 final types = <Type>[];
234 final exceptions = <Type>[];
235
236 for (final type in orderByName(library.types)) {
237 if (type.isTop) continue;
238 if (type.name.startsWith('_')) continue;
239
240 if (type.name.endsWith('Exception')) {
241 exceptions.add(type);
242 } else {
243 types.add(type);
244 }
245 }
246
247 if ((types.length == 0) && (exceptions.length == 0)) return;
248
249 writeType(String icon, Type type) {
250 write('<li>');
251 if (_currentType == type) {
252 write(
253 '<div class="icon-$icon"></div><strong>${typeName(type)}</strong>');
254 } else {
255 write(a(typeUrl(type),
256 '<div class="icon-$icon"></div>${typeName(type)}'));
257 }
258 writeln('</li>');
259 }
260
261 writeln('<ul>');
262 types.forEach((type) => writeType(type.isClass ? 'class' : 'interface',
263 type));
264 exceptions.forEach((type) => writeType('exception', type));
265 writeln('</ul>');
266 }
267
268 docLibrary(Library library) {
269 _totalLibraries++;
270 _currentLibrary = library;
271 _currentType = null;
272
273 startFile(libraryUrl(library));
274 writeHeader(library.name);
275 writeln('<h1>Library <strong>${library.name}</strong></h1>');
276
277 // Look for a comment for the entire library.
278 final comment = _comments.findLibrary(library.baseSource);
279 if (comment != null) {
280 final html = md.markdownToHtml(comment);
281 writeln('<div class="doc">$html</div>');
282 }
283
284 // Document the top-level members.
285 docMembers(library.topType);
286
287 // Document the types.
288 final classes = <Type>[];
289 final interfaces = <Type>[];
290 final exceptions = <Type>[];
291
292 for (final type in orderByName(library.types)) {
293 if (type.isTop) continue;
294 if (type.name.startsWith('_')) continue;
295
296 if (type.name.endsWith('Exception')) {
297 exceptions.add(type);
298 } else if (type.isClass) {
299 classes.add(type);
300 } else {
301 interfaces.add(type);
302 }
303 }
304
305 docTypes(classes, 'Classes');
306 docTypes(interfaces, 'Interfaces');
307 docTypes(exceptions, 'Exceptions');
308
309 writeFooter();
310 endFile();
311
312 for (final type in library.types.getValues()) {
313 if (!type.isTop) docType(type);
314 }
315 }
316
317 docTypes(List<Type> types, String header) {
318 if (types.length == 0) return;
319
320 writeln('<h3>$header</h3>');
321
322 for (final type in types) {
323 writeln(
324 '''
325 <div class="type">
326 <h4>
327 ${a(typeUrl(type), "<strong>${typeName(type)}</strong>")}
328 </h4>
329 </div>
330 ''');
331 }
332 }
333
334 docType(Type type) {
335 _totalTypes++;
336 _currentType = type;
337
338 startFile(typeUrl(type));
339
340 final typeTitle =
341 '${type.isClass ? "Class" : "Interface"} ${typeName(type)}';
342 writeHeader('Library ${type.library.name} / $typeTitle');
343 writeln(
344 '''
345 <h1>${a(libraryUrl(type.library),
346 "Library <strong>${type.library.name}</strong>")}</h1>
347 <h2>${type.isClass ? "Class" : "Interface"}
348 <strong>${typeName(type, showBounds: true)}</strong></h2>
349 ''');
350
351 docInheritance(type);
352
353 docCode(type.span, getTypeComment(type));
354 docConstructors(type);
355 docMembers(type);
356
357 writeFooter();
358 endFile();
359 }
360
361 /** Document the superclass, superinterfaces and factory of [Type]. */
362 docInheritance(Type type) {
363 final isSubclass = (type.parent != null) && !type.parent.isObject;
364
365 Type factory;
366 if (type.definition is TypeDefinition) {
367 TypeDefinition definition = type.definition;
368 if (definition.factoryType != null) {
369 factory = definition.factoryType.type;
370 }
371 }
372
373 if (isSubclass ||
374 (type.interfaces != null && type.interfaces.length > 0) ||
375 (factory != null)) {
376 writeln('<p>');
377
378 if (isSubclass) {
379 write('Extends ${typeReference(type.parent)}. ');
380 }
381
382 if (type.interfaces != null && type.interfaces.length > 0) {
383 var interfaceStr = joinWithCommas(map(type.interfaces, typeReference));
384 write('Implements ${interfaceStr}. ');
385 }
386
387 if (factory != null) {
388 write('Has factory class ${typeReference(factory)}.');
389 }
390 }
391 }
392
393 /** Document the constructors for [Type], if any. */
394 docConstructors(Type type) {
395 final names = type.constructors.getKeys().filter(
396 (name) => !name.startsWith('_'));
397
398 if (names.length > 0) {
399 writeln('<h3>Constructors</h3>');
400 names.sort((x, y) => x.toUpperCase().compareTo(y.toUpperCase()));
401
402 for (final name in names) {
403 docMethod(type, type.constructors[name], constructorName: name);
404 }
405 }
406 }
407
408 void docMembers(Type type) {
409 // Collect the different kinds of members.
410 final methods = [];
411 final fields = [];
412
413 for (final member in orderByName(type.members)) {
414 if (member.name.startsWith('_')) continue;
415
416 if (member.isProperty) {
417 if (member.canGet) methods.add(member.getter);
418 if (member.canSet) methods.add(member.setter);
419 } else if (member.isMethod) {
420 methods.add(member);
421 } else if (member.isField) {
422 fields.add(member);
423 }
424 }
425
426 if (methods.length > 0) {
427 writeln('<h3>Methods</h3>');
428 for (final method in methods) docMethod(type, method);
429 }
430
431 if (fields.length > 0) {
432 writeln('<h3>Fields</h3>');
433 for (final field in fields) docField(type, field);
434 }
435 }
436
437 /**
438 * Documents the [method] in type [type]. Handles all kinds of methods
439 * including getters, setters, and constructors.
440 */
441 docMethod(Type type, MethodMember method, [String constructorName = null]) {
442 _totalMembers++;
443 _currentMember = method;
444
445 writeln('<div class="method"><h4 id="${memberAnchor(method)}">');
446
447 if (includeSource) {
448 writeln('<span class="show-code">Code</span>');
449 }
450
451 if (method.isStatic && !type.isTop) {
452 write('static ');
453 }
454
455 if (method.isConstructor) {
456 write(method.isConst ? 'const ' : 'new ');
457 }
458
459 if (constructorName == null) {
460 annotateType(type, method.returnType);
461 }
462
463 // Translate specially-named methods: getters, setters, operators.
464 var name = method.name;
465 if (name.startsWith('get:')) {
466 // Getter.
467 name = 'get ${name.substring(4)}';
468 } else if (name.startsWith('set:')) {
469 // Setter.
470 name = 'set ${name.substring(4)}';
471 } else {
472 // See if it's an operator.
473 name = TokenKind.rawOperatorFromMethod(name);
474 if (name == null) {
475 name = method.name;
476 } else {
477 name = 'operator $name';
478 }
479 }
480
481 write('<strong>$name</strong>');
482
483 // Named constructors.
484 if (constructorName != null && constructorName != '') {
485 write('.');
486 write(constructorName);
487 }
488
489 docParamList(type, method);
490
491 write(''' <a class="anchor-link" href="#${memberAnchor(method)}"
492 title="Permalink to ${typeName(type)}.$name">#</a>''');
493 writeln('</h4>');
494
495 docCode(method.span, getMethodComment(method), showCode: true);
496
497 writeln('</div>');
498 }
499
500 /** Documents the field [field] of type [type]. */
501 docField(Type type, FieldMember field) {
502 _totalMembers++;
503 _currentMember = field;
504
505 writeln('<div class="field"><h4 id="${memberAnchor(field)}">');
506
507 if (includeSource) {
508 writeln('<span class="show-code">Code</span>');
509 }
510
511 if (field.isStatic && !type.isTop) {
512 write('static ');
513 }
514
515 if (field.isFinal) {
516 write('final ');
517 } else if (field.type.name == 'Dynamic') {
518 write('var ');
519 }
520
521 annotateType(type, field.type);
522 write(
523 '''
524 <strong>${field.name}</strong> <a class="anchor-link"
525 href="#${memberAnchor(field)}"
526 title="Permalink to ${typeName(type)}.${field.name}">#</a>
353 </h4> 527 </h4>
354 </div>
355 '''); 528 ''');
529
530 docCode(field.span, getFieldComment(field), showCode: true);
531 writeln('</div>');
532 }
533
534 docParamList(Type enclosingType, MethodMember member) {
535 write('(');
536 bool first = true;
537 bool inOptionals = false;
538 for (final parameter in member.parameters) {
539 if (!first) write(', ');
540
541 if (!inOptionals && parameter.isOptional) {
542 write('[');
543 inOptionals = true;
544 }
545
546 annotateType(enclosingType, parameter.type, parameter.name);
547
548 // Show the default value for named optional parameters.
549 if (parameter.isOptional && parameter.hasDefaultValue) {
550 write(' = ');
551 // TODO(rnystrom): Using the definition text here is a bit cheap.
552 // We really should be pretty-printing the AST so that if you have:
553 // foo([arg = 1 + /* comment */ 2])
554 // the docs should just show:
555 // foo([arg = 1 + 2])
556 // For now, we'll assume you don't do that.
557 write(parameter.definition.value.span.text);
558 }
559
560 first = false;
561 }
562
563 if (inOptionals) write(']');
564 write(')');
565 }
566
567 /**
568 * Documents the code contained within [span] with [comment]. If [showCode]
569 * is `true` (and [includeSource] is set), also includes the source code.
570 */
571 docCode(SourceSpan span, String comment, [bool showCode = false]) {
572 writeln('<div class="doc">');
573 if (comment != null) {
574 writeln(md.markdownToHtml(comment));
575 }
576
577 if (includeSource && showCode) {
578 writeln('<pre class="source">');
579 write(formatCode(span));
580 writeln('</pre>');
581 }
582
583 writeln('</div>');
584 }
585
586 /** Get the doc comment associated with the given type. */
587 String getTypeComment(Type type) => _comments.find(type.span);
588
589 /** Get the doc comment associated with the given method. */
590 String getMethodComment(MethodMember method) => _comments.find(method.span);
591
592 /** Get the doc comment associated with the given field. */
593 String getFieldComment(FieldMember field) => _comments.find(field.span);
594
595 /**
596 * Creates a hyperlink. Handles turning the [href] into an appropriate
597 * relative path from the current file.
598 */
599 String a(String href, String contents, [String class]) {
600 final css = class == null ? '' : ' class="$class"';
601 return '<a href="${relativePath(href)}"$css>$contents</a>';
602 }
603
604 /**
605 * Writes a type annotation for the given type and (optional) parameter name.
606 */
607 annotateType(Type enclosingType, Type type, [String paramName = null]) {
608 // Don't bother explicitly displaying Dynamic.
609 if (type.isVar) {
610 if (paramName !== null) write(paramName);
611 return;
612 }
613
614 // For parameters, handle non-typedefed function types.
615 if (paramName !== null) {
616 final call = type.getCallMethod();
617 if (call != null) {
618 annotateType(enclosingType, call.returnType);
619 write(paramName);
620
621 docParamList(enclosingType, call);
622 return;
623 }
624 }
625
626 linkToType(enclosingType, type);
627
628 write(' ');
629 if (paramName !== null) write(paramName);
630 }
631
632 /** Writes a link to a human-friendly string representation for a type. */
633 linkToType(Type enclosingType, Type type) {
634 if (type is ParameterType) {
635 // If we're using a type parameter within the body of a generic class then
636 // just link back up to the class.
637 write(a(typeUrl(enclosingType), type.name));
638 return;
639 }
640
641 // Link to the type.
642 // Use .genericType to avoid writing the <...> here.
643 write(a(typeUrl(type), type.genericType.name));
644
645 // See if it's a generic type.
646 if (type.isGeneric) {
647 // TODO(rnystrom): This relies on a weird corner case of frog. Currently,
648 // the only time we get into this case is when we have a "raw" generic
649 // that's been instantiated with Dynamic for all type arguments. It's kind
650 // of strange that frog works that way, but we take advantage of it to
651 // show raw types without any type arguments.
652 return;
653 }
654
655 // See if it's an instantiation of a generic type.
656 final typeArgs = type.typeArgsInOrder;
657 if (typeArgs != null) {
658 write('&lt;');
659 bool first = true;
660 for (final arg in typeArgs) {
661 if (!first) write(', ');
662 first = false;
663 linkToType(enclosingType, arg);
664 }
665 write('&gt;');
666 }
667 }
668
669 /** Creates a linked cross reference to [type]. */
670 typeReference(Type type) {
671 // TODO(rnystrom): Do we need to handle ParameterTypes here like
672 // annotation() does?
673 return a(typeUrl(type), typeName(type), class: 'crossref');
674 }
675
676 /** Generates a human-friendly string representation for a type. */
677 typeName(Type type, [bool showBounds = false]) {
678 // See if it's a generic type.
679 if (type.isGeneric) {
680 final typeParams = [];
681 for (final typeParam in type.genericType.typeParameters) {
682 if (showBounds &&
683 (typeParam.extendsType != null) &&
684 !typeParam.extendsType.isObject) {
685 final bound = typeName(typeParam.extendsType, showBounds: true);
686 typeParams.add('${typeParam.name} extends $bound');
687 } else {
688 typeParams.add(typeParam.name);
689 }
690 }
691
692 final params = Strings.join(typeParams, ', ');
693 return '${type.name}&lt;$params&gt;';
694 }
695
696 // See if it's an instantiation of a generic type.
697 final typeArgs = type.typeArgsInOrder;
698 if (typeArgs != null) {
699 final args = Strings.join(map(typeArgs, (arg) => typeName(arg)), ', ');
700 return '${type.genericType.name}&lt;$args&gt;';
701 }
702
703 // Regular type.
704 return type.name;
705 }
706
707 /**
708 * Takes a string of Dart code and turns it into sanitized HTML.
709 */
710 formatCode(SourceSpan span) {
711 // Remove leading indentation to line up with first line.
712 final column = getSpanColumn(span);
713 final lines = span.text.split('\n');
714 // TODO(rnystrom): Dirty hack.
715 for (final i = 1; i < lines.length; i++) {
716 lines[i] = unindent(lines[i], column);
717 }
718
719 final code = Strings.join(lines, '\n');
720
721 // Syntax highlight.
722 return classifySource(new SourceFile('', code));
723 }
724
725 /**
726 * This will be called whenever a doc comment hits a `[name]` in square
727 * brackets. It will try to figure out what the name refers to and link or
728 * style it appropriately.
729 */
730 md.Node resolveNameReference(String name) {
731 makeLink(String href) {
732 final anchor = new md.Element.text('a', name);
733 anchor.attributes['href'] = relativePath(href);
734 anchor.attributes['class'] = 'crossref';
735 return anchor;
736 }
737
738 findMember(Type type) {
739 final member = type.members[name];
740 if (member == null) return null;
741
742 // Special case: if the member we've resolved is a property (i.e. it wraps
743 // a getter and/or setter then *that* member itself won't be on the docs,
744 // just the getter or setter will be. So pick one of those to link to.
745 if (member.isProperty) {
746 return member.canGet ? member.getter : member.setter;
747 }
748
749 return member;
750 }
751
752 // See if it's a parameter of the current method.
753 if (_currentMember != null) {
754 for (final parameter in _currentMember.parameters) {
755 if (parameter.name == name) {
756 final element = new md.Element.text('span', name);
757 element.attributes['class'] = 'param';
758 return element;
759 }
760 }
761 }
762
763 // See if it's another member of the current type.
764 if (_currentType != null) {
765 final member = findMember(_currentType);
766 if (member != null) {
767 return makeLink(memberUrl(member));
768 }
769 }
770
771 // See if it's another type in the current library.
772 if (_currentLibrary != null) {
773 final type = _currentLibrary.types[name];
774 if (type != null) {
775 return makeLink(typeUrl(type));
776 }
777
778 // See if it's a top-level member in the current library.
779 final member = findMember(_currentLibrary.topType);
780 if (member != null) {
781 return makeLink(memberUrl(member));
782 }
783 }
784
785 // TODO(rnystrom): Should also consider:
786 // * Names imported by libraries this library imports.
787 // * Type parameters of the enclosing type.
788
789 return new md.Element.text('code', name);
790 }
791
792 // TODO(rnystrom): Move into SourceSpan?
793 int getSpanColumn(SourceSpan span) {
794 final line = span.file.getLine(span.start);
795 return span.file.getColumn(line, span.start);
356 } 796 }
357 } 797 }
358
359 docType(Type type) {
360 _totalTypes++;
361 _currentType = type;
362
363 startFile(typeUrl(type));
364
365 final typeTitle = '${type.isClass ? "Class" : "Interface"} ${typeName(type)}';
366 writeHeader('Library ${type.library.name} / $typeTitle');
367 writeln(
368 '''
369 <h1>${a(libraryUrl(type.library),
370 "Library <strong>${type.library.name}</strong>")}</h1>
371 <h2>${type.isClass ? "Class" : "Interface"}
372 <strong>${typeName(type, showBounds: true)}</strong></h2>
373 ''');
374
375 docInheritance(type);
376 docCode(type.span, _runDocumenters(type, _typeDocumenters));
377 docConstructors(type);
378 docMembers(type);
379
380 writeFooter();
381 endFile();
382 }
383
384 void docMembers(Type type) {
385 // Collect the different kinds of members.
386 final methods = [];
387 final fields = [];
388
389 for (final member in orderByName(type.members)) {
390 if (member.name.startsWith('_')) continue;
391
392 if (member.isProperty) {
393 if (member.canGet) methods.add(member.getter);
394 if (member.canSet) methods.add(member.setter);
395 } else if (member.isMethod) {
396 methods.add(member);
397 } else if (member.isField) {
398 fields.add(member);
399 }
400 }
401
402 if (methods.length > 0) {
403 writeln('<h3>Methods</h3>');
404 for (final method in methods) docMethod(type, method);
405 }
406
407 if (fields.length > 0) {
408 writeln('<h3>Fields</h3>');
409 for (final field in fields) docField(type, field);
410 }
411 }
412
413 /** Document the superclass, superinterfaces and factory of [Type]. */
414 docInheritance(Type type) {
415 final isSubclass = (type.parent != null) && !type.parent.isObject;
416
417 Type factory;
418 if (type.definition is TypeDefinition) {
419 TypeDefinition definition = type.definition;
420 if (definition.factoryType != null) {
421 factory = definition.factoryType.type;
422 }
423 }
424
425 if (isSubclass ||
426 (type.interfaces != null && type.interfaces.length > 0) ||
427 (factory != null)) {
428 writeln('<p>');
429
430 if (isSubclass) {
431 write('Extends ${typeReference(type.parent)}. ');
432 }
433
434 if (type.interfaces != null && type.interfaces.length > 0) {
435 var interfaceStr = joinWithCommas(map(type.interfaces, typeReference));
436 write('Implements ${interfaceStr}. ');
437 }
438
439 if (factory != null) {
440 write('Has factory class ${typeReference(factory)}.');
441 }
442 }
443 }
444
445 /** Document the constructors for [Type], if any. */
446 docConstructors(Type type) {
447 final names = type.constructors.getKeys().filter(
448 (name) => !name.startsWith('_'));
449
450 if (names.length > 0) {
451 writeln('<h3>Constructors</h3>');
452 names.sort((x, y) => x.toUpperCase().compareTo(y.toUpperCase()));
453
454 for (final name in names) {
455 docMethod(type, type.constructors[name], constructorName: name);
456 }
457 }
458 }
459
460 /**
461 * Documents the [method] in type [type]. Handles all kinds of methods
462 * including getters, setters, and constructors.
463 */
464 docMethod(Type type, MethodMember method, [String constructorName = null]) {
465 _totalMembers++;
466 _currentMember = method;
467
468 writeln('<div class="method"><h4 id="${memberAnchor(method)}">');
469
470 if (includeSource) {
471 writeln('<span class="show-code">Code</span>');
472 }
473
474 if (method.isStatic && !type.isTop) {
475 write('static ');
476 }
477
478 if (method.isConstructor) {
479 write(method.isConst ? 'const ' : 'new ');
480 }
481
482 if (constructorName == null) {
483 annotateType(type, method.returnType);
484 }
485
486 // Translate specially-named methods: getters, setters, operators.
487 var name = method.name;
488 if (name.startsWith('get:')) {
489 // Getter.
490 name = 'get ${name.substring(4)}';
491 } else if (name.startsWith('set:')) {
492 // Setter.
493 name = 'set ${name.substring(4)}';
494 } else {
495 // See if it's an operator.
496 name = TokenKind.rawOperatorFromMethod(name);
497 if (name == null) {
498 name = method.name;
499 } else {
500 name = 'operator $name';
501 }
502 }
503
504 write('<strong>$name</strong>');
505
506 // Named constructors.
507 if (constructorName != null && constructorName != '') {
508 write('.');
509 write(constructorName);
510 }
511
512 docParamList(type, method);
513
514 write(''' <a class="anchor-link" href="#${memberAnchor(method)}"
515 title="Permalink to ${typeName(type)}.$name">#</a>''');
516 writeln('</h4>');
517
518 docCode(method.span, _runDocumenters(method, _methodDocumenters),
519 showCode: true);
520
521 writeln('</div>');
522 }
523
524 docParamList(Type enclosingType, MethodMember member) {
525 write('(');
526 bool first = true;
527 bool inOptionals = false;
528 for (final parameter in member.parameters) {
529 if (!first) write(', ');
530
531 if (!inOptionals && parameter.isOptional) {
532 write('[');
533 inOptionals = true;
534 }
535
536 annotateType(enclosingType, parameter.type, parameter.name);
537
538 // Show the default value for named optional parameters.
539 if (parameter.isOptional && parameter.hasDefaultValue) {
540 write(' = ');
541 // TODO(rnystrom): Using the definition text here is a bit cheap.
542 // We really should be pretty-printing the AST so that if you have:
543 // foo([arg = 1 + /* comment */ 2])
544 // the docs should just show:
545 // foo([arg = 1 + 2])
546 // For now, we'll assume you don't do that.
547 write(parameter.definition.value.span.text);
548 }
549
550 first = false;
551 }
552
553 if (inOptionals) write(']');
554 write(')');
555 }
556
557 /** Documents the field [field] of type [type]. */
558 docField(Type type, FieldMember field) {
559 _totalMembers++;
560 _currentMember = field;
561
562 writeln('<div class="field"><h4 id="${memberAnchor(field)}">');
563
564 if (includeSource) {
565 writeln('<span class="show-code">Code</span>');
566 }
567
568 if (field.isStatic && !type.isTop) {
569 write('static ');
570 }
571
572 if (field.isFinal) {
573 write('final ');
574 } else if (field.type.name == 'Dynamic') {
575 write('var ');
576 }
577
578 annotateType(type, field.type);
579 write(
580 '''
581 <strong>${field.name}</strong> <a class="anchor-link"
582 href="#${memberAnchor(field)}"
583 title="Permalink to ${typeName(type)}.${field.name}">#</a>
584 </h4>
585 ''');
586
587 docCode(field.span, _runDocumenters(field, _fieldDocumenters),
588 showCode: true);
589 writeln('</div>');
590 }
591
592 /**
593 * Creates a hyperlink. Handles turning the [href] into an appropriate relative
594 * path from the current file.
595 */
596 String a(String href, String contents, [String class]) {
597 final css = class == null ? '' : ' class="$class"';
598 return '<a href="${relativePath(href)}"$css>$contents</a>';
599 }
600
601 /** Generates a human-friendly string representation for a type. */
602 typeName(Type type, [bool showBounds = false]) {
603 // See if it's a generic type.
604 if (type.isGeneric) {
605 final typeParams = [];
606 for (final typeParam in type.genericType.typeParameters) {
607 if (showBounds &&
608 (typeParam.extendsType != null) &&
609 !typeParam.extendsType.isObject) {
610 final bound = typeName(typeParam.extendsType, showBounds: true);
611 typeParams.add('${typeParam.name} extends $bound');
612 } else {
613 typeParams.add(typeParam.name);
614 }
615 }
616
617 final params = Strings.join(typeParams, ', ');
618 return '${type.name}&lt;$params&gt;';
619 }
620
621 // See if it's an instantiation of a generic type.
622 final typeArgs = type.typeArgsInOrder;
623 if (typeArgs != null) {
624 final args = Strings.join(map(typeArgs, typeName), ', ');
625 return '${type.genericType.name}&lt;$args&gt;';
626 }
627
628 // Regular type.
629 return type.name;
630 }
631
632 /** Writes a link to a human-friendly string representation for a type. */
633 linkToType(Type enclosingType, Type type) {
634 if (type is ParameterType) {
635 // If we're using a type parameter within the body of a generic class then
636 // just link back up to the class.
637 write(a(typeUrl(enclosingType), type.name));
638 return;
639 }
640
641 // Link to the type.
642 // Use .genericType to avoid writing the <...> here.
643 write(a(typeUrl(type), type.genericType.name));
644
645 // See if it's a generic type.
646 if (type.isGeneric) {
647 // TODO(rnystrom): This relies on a weird corner case of frog. Currently,
648 // the only time we get into this case is when we have a "raw" generic
649 // that's been instantiated with Dynamic for all type arguments. It's kind
650 // of strange that frog works that way, but we take advantage of it to
651 // show raw types without any type arguments.
652 return;
653 }
654
655 // See if it's an instantiation of a generic type.
656 final typeArgs = type.typeArgsInOrder;
657 if (typeArgs != null) {
658 write('&lt;');
659 bool first = true;
660 for (final arg in typeArgs) {
661 if (!first) write(', ');
662 first = false;
663 linkToType(enclosingType, arg);
664 }
665 write('&gt;');
666 }
667 }
668
669 /** Creates a linked cross reference to [type]. */
670 typeReference(Type type) {
671 // TODO(rnystrom): Do we need to handle ParameterTypes here like
672 // annotation() does?
673 return a(typeUrl(type), typeName(type), class: 'crossref');
674 }
675
676 /**
677 * Writes a type annotation for the given type and (optional) parameter name.
678 */
679 annotateType(Type enclosingType, Type type, [String paramName = null]) {
680 // Don't bother explicitly displaying Dynamic.
681 if (type.isVar) {
682 if (paramName !== null) write(paramName);
683 return;
684 }
685
686 // For parameters, handle non-typedefed function types.
687 if (paramName !== null) {
688 final call = type.getCallMethod();
689 if (call != null) {
690 annotateType(enclosingType, call.returnType);
691 write(paramName);
692
693 docParamList(enclosingType, call);
694 return;
695 }
696 }
697
698 linkToType(enclosingType, type);
699
700 write(' ');
701 if (paramName !== null) write(paramName);
702 }
703
704
705 /**
706 * This will be called whenever a doc comment hits a `[name]` in square
707 * brackets. It will try to figure out what the name refers to and link or
708 * style it appropriately.
709 */
710 md.Node resolveNameReference(String name) {
711 makeLink(String href) {
712 final anchor = new md.Element.text('a', name);
713 anchor.attributes['href'] = relativePath(href);
714 anchor.attributes['class'] = 'crossref';
715 return anchor;
716 }
717
718 findMember(Type type) {
719 final member = type.members[name];
720 if (member == null) return null;
721
722 // Special case: if the member we've resolved is a property (i.e. it wraps
723 // a getter and/or setter then *that* member itself won't be on the docs,
724 // just the getter or setter will be. So pick one of those to link to.
725 if (member.isProperty) {
726 return member.canGet ? member.getter : member.setter;
727 }
728
729 return member;
730 }
731
732 // See if it's a parameter of the current method.
733 if (_currentMember != null) {
734 for (final parameter in _currentMember.parameters) {
735 if (parameter.name == name) {
736 final element = new md.Element.text('span', name);
737 element.attributes['class'] = 'param';
738 return element;
739 }
740 }
741 }
742
743 // See if it's another member of the current type.
744 if (_currentType != null) {
745 final member = findMember(_currentType);
746 if (member != null) {
747 return makeLink(memberUrl(member));
748 }
749 }
750
751 // See if it's another type in the current library.
752 if (_currentLibrary != null) {
753 final type = _currentLibrary.types[name];
754 if (type != null) {
755 return makeLink(typeUrl(type));
756 }
757
758 // See if it's a top-level member in the current library.
759 final member = findMember(_currentLibrary.topType);
760 if (member != null) {
761 return makeLink(memberUrl(member));
762 }
763 }
764
765 // TODO(rnystrom): Should also consider:
766 // * Names imported by libraries this library imports.
767 // * Type parameters of the enclosing type.
768
769 return new md.Element.text('code', name);
770 }
771
772 /**
773 * Documents the code contained within [span]. Will include the previous
774 * Dartdoc associated with that span if found, and will include the syntax
775 * highlighted code itself if desired.
776 */
777 docCode(SourceSpan span, String extraMarkdown, [bool showCode = false]) {
778 if (span == null) return;
779
780 writeln('<div class="doc">');
781 final comment = findComment(span);
782 if (comment != null) {
783 writeln(md.markdownToHtml('${comment}\n\n${extraMarkdown}'));
784 } else {
785 writeln(md.markdownToHtml(extraMarkdown));
786 }
787
788 if (includeSource && showCode) {
789 writeln('<pre class="source">');
790 write(formatCode(span));
791 writeln('</pre>');
792 }
793
794 writeln('</div>');
795 }
796
797 /** Finds the doc comment preceding the given source span, if there is one. */
798 findComment(SourceSpan span) => findCommentInFile(span.file, span.start);
799
800 /** Finds the doc comment preceding the given source span, if there is one. */
801 findCommentInFile(SourceFile file, int position) {
802 // Get the doc comments for this file.
803 final fileComments = _comments.putIfAbsent(file.filename,
804 () => parseDocComments(file));
805
806 return fileComments[position];
807 }
808
809 parseDocComments(SourceFile file) {
810 final comments = new Map<int, String>();
811
812 final tokenizer = new Tokenizer(file, false);
813 var lastComment = null;
814
815 while (true) {
816 final token = tokenizer.next();
817 if (token.kind == TokenKind.END_OF_FILE) break;
818
819 if (token.kind == TokenKind.COMMENT) {
820 final text = token.text;
821 if (text.startsWith('/**')) {
822 // Remember that we've encountered a doc comment.
823 lastComment = stripComment(token.text);
824 } else if (text.startsWith('///')) {
825 var line = text.substring(3, text.length);
826 // Allow a leading space.
827 if (line.startsWith(' ')) line = line.substring(1, text.length);
828 if (lastComment == null) {
829 lastComment = line;
830 } else {
831 lastComment = '$lastComment$line';
832 }
833 }
834 } else if (token.kind == TokenKind.WHITESPACE) {
835 // Ignore whitespace tokens.
836 } else if (token.kind == TokenKind.HASH) {
837 // Look for #library() to find the library comment.
838 final next = tokenizer.next();
839 if ((lastComment != null) && (next.kind == TokenKind.LIBRARY)) {
840 comments[_libraryDoc] = lastComment;
841 lastComment = null;
842 }
843 } else {
844 if (lastComment != null) {
845 // We haven't attached the last doc comment to something yet, so stick
846 // it to this token.
847 comments[token.start] = lastComment;
848 lastComment = null;
849 }
850 }
851 }
852
853 return comments;
854 }
855
856 /**
857 * Takes a string of Dart code and turns it into sanitized HTML.
858 */
859 formatCode(SourceSpan span) {
860 // Remove leading indentation to line up with first line.
861 final column = getSpanColumn(span);
862 final lines = span.text.split('\n');
863 // TODO(rnystrom): Dirty hack.
864 for (final i = 1; i < lines.length; i++) {
865 lines[i] = unindent(lines[i], column);
866 }
867
868 final code = Strings.join(lines, '\n');
869
870 // Syntax highlight.
871 return classifySource(new SourceFile('', code));
872 }
873
874 // TODO(rnystrom): Move into SourceSpan?
875 int getSpanColumn(SourceSpan span) {
876 final line = span.file.getLine(span.start);
877 return span.file.getColumn(line, span.start);
878 }
879
880 /**
881 * Pulls the raw text out of a doc comment (i.e. removes the comment
882 * characters).
883 */
884 stripComment(comment) {
885 StringBuffer buf = new StringBuffer();
886
887 for (final line in comment.split('\n')) {
888 line = line.trim();
889 if (line.startsWith('/**')) line = line.substring(3, line.length);
890 if (line.endsWith('*/')) line = line.substring(0, line.length - 2);
891 line = line.trim();
892 if (line.startsWith('* ')) {
893 line = line.substring(2, line.length);
894 } else if (line.startsWith('*')) {
895 line = line.substring(1, line.length);
896 }
897
898 buf.add(line);
899 buf.add('\n');
900 }
901
902 return buf.toString();
903 }
904
905 /** Register a callback to add additional documentation to a type. */
906 addTypeDocumenter(TypeDocumenter fn) => _typeDocumenters.add(fn);
907
908 /** Register a callback to add additional documentation to a method. */
909 addMethodDocumenter(MethodDocumenter fn) => _methodDocumenters.add(fn);
910
911 /** Register a callback to add additional documentation to a field. */
912 addFieldDocumenter(FieldDocumenter fn) => _fieldDocumenters.add(fn);
OLDNEW
« utils/dartdoc/comment_map.dart ('K') | « utils/dartdoc/comment_map.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698