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

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: Respond to review. 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/comment_map.dart ('k') | no next file » | 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 /** 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 CommentMap _comments;
118 79
119 // Patch in support for [:...:]-style code to the markdown parser. 80 /** The library that we're currently generating docs for. */
120 // TODO(rnystrom): Markdown already has syntax for this. Phase this out? 81 Library _currentLibrary;
121 md.InlineParser.syntaxes.insertRange(0, 1, 82
122 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); 83 /** The type that we're currently generating docs for. */
123 84 Type _currentType;
124 md.setImplicitLinkResolver(resolveNameReference); 85
125 } 86 /** The member that we're currently generating docs for. */
126 87 Member _currentMember;
127 document(String entrypoint) { 88
128 try { 89 int _totalLibraries = 0;
129 var oldDietParse = options.dietParse; 90 int _totalTypes = 0;
130 options.dietParse = true; 91 int _totalMembers = 0;
131 92
132 // Handle the built-in entrypoints. 93 Dartdoc()
133 switch (entrypoint) { 94 : _comments = new CommentMap() {
134 case 'corelib': 95 // Patch in support for [:...:]-style code to the markdown parser.
135 world.getOrAddLibrary('dart:core'); 96 // TODO(rnystrom): Markdown already has syntax for this. Phase this out?
136 world.getOrAddLibrary('dart:coreimpl'); 97 md.InlineParser.syntaxes.insertRange(0, 1,
137 world.getOrAddLibrary('dart:json'); 98 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]'));
138 world.process(); 99
139 break; 100 md.setImplicitLinkResolver(resolveNameReference);
140 101 }
141 case 'dom': 102
142 world.getOrAddLibrary('dart:core'); 103 document(String entrypoint) {
143 world.getOrAddLibrary('dart:coreimpl'); 104 try {
144 world.getOrAddLibrary('dart:json'); 105 var oldDietParse = options.dietParse;
145 world.getOrAddLibrary('dart:dom'); 106 options.dietParse = true;
146 world.process(); 107
147 break; 108 // Handle the built-in entrypoints.
148 109 switch (entrypoint) {
149 case 'html': 110 case 'corelib':
150 world.getOrAddLibrary('dart:core'); 111 world.getOrAddLibrary('dart:core');
151 world.getOrAddLibrary('dart:coreimpl'); 112 world.getOrAddLibrary('dart:coreimpl');
152 world.getOrAddLibrary('dart:json'); 113 world.getOrAddLibrary('dart:json');
153 world.getOrAddLibrary('dart:dom'); 114 world.process();
154 world.getOrAddLibrary('dart:html'); 115 break;
155 world.process(); 116
156 break; 117 case 'dom':
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.getOrAddLibrary('dart:dom');
161 } 122 world.process();
162 123 break;
163 world.resolveAll(); 124
164 125 case 'html':
165 // Generate the docs. 126 world.getOrAddLibrary('dart:core');
166 docIndex(); 127 world.getOrAddLibrary('dart:coreimpl');
167 for (final library in world.libraries.getValues()) { 128 world.getOrAddLibrary('dart:json');
168 docLibrary(library); 129 world.getOrAddLibrary('dart:dom');
169 } 130 world.getOrAddLibrary('dart:html');
170 } finally { 131 world.process();
171 options.dietParse = oldDietParse; 132 break;
172 } 133
173 } 134 default:
174 135 // Normal entrypoint script.
175 printStats(num elapsed) { 136 world.processDartScript(entrypoint);
176 print('Documented $_totalLibraries libraries, $_totalTypes types, and ' + 137 }
177 '$_totalMembers members in ${elapsed}msec.'); 138
178 } 139 world.resolveAll();
179 140
180 writeHeader(String title) { 141 // Generate the docs.
181 writeln( 142 docIndex();
182 ''' 143 for (final library in world.libraries.getValues()) {
183 <!DOCTYPE html> 144 docLibrary(library);
184 <html> 145 }
185 <head> 146 } finally {
186 <meta charset="utf-8"> 147 options.dietParse = oldDietParse;
187 <title>$title</title> 148 }
188 <link rel="stylesheet" type="text/css" 149 }
189 href="${relativePath('styles.css')}" /> 150
190 <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700,8 00" rel="stylesheet" type="text/css"> 151 writeHeader(String title) {
191 <script src="${relativePath('interact.js')}"></script>
192 </head>
193 <body>
194 <div class="page">
195 ''');
196 docNavigation();
197 writeln('<div class="content">');
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( 152 writeln(
219 ''' 153 '''
220 <h4>${a(libraryUrl(library), library.name)}</h4> 154 <!DOCTYPE html>
155 <html>
156 <head>
157 <meta charset="utf-8">
158 <title>$title</title>
159 <link rel="stylesheet" type="text/css"
160 href="${relativePath('styles.css')}" />
161 <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700 ,800" rel="stylesheet" type="text/css">
162 <script src="${relativePath('interact.js')}"></script>
163 </head>
164 <body>
165 <div class="page">
221 '''); 166 ''');
222 } 167 docNavigation();
223 168 writeln('<div class="content">');
224 writeFooter(); 169 }
225 endFile(); 170
226 } 171 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( 172 writeln(
349 ''' 173 '''
350 <div class="type"> 174 </div>
351 <h4> 175 <div class="footer"</div>
352 ${a(typeUrl(type), "<strong>${typeName(type)}</strong>")} 176 </body></html>
177 ''');
178 }
179
180 docIndex() {
181 startFile('index.html');
182
183 writeHeader('Dart Documentation');
184
185 writeln('<h1>Dart Documentation</h1>');
186 writeln('<h3>Libraries</h3>');
187
188 for (final library in orderByName(world.libraries)) {
189 writeln(
190 '''
191 <h4>${a(libraryUrl(library), library.name)}</h4>
192 ''');
193 }
194
195 writeFooter();
196 endFile();
197 }
198
199 docNavigation() {
200 writeln(
201 '''
202 <div class="nav">
203 <h1>${a("index.html", "Dart Documentation")}</h1>
204 ''');
205
206 for (final library in orderByName(world.libraries)) {
207 write('<h2><div class="icon-library"></div>');
208
209 if ((_currentLibrary == library) && (_currentType == null)) {
210 write('<strong>${library.name}</strong>');
211 } else {
212 write('${a(libraryUrl(library), library.name)}');
213 }
214 write('</h2>');
215
216 // Only expand classes in navigation for current library.
217 if (_currentLibrary == library) docLibraryNavigation(library);
218 }
219
220 writeln('</div>');
221 }
222
223 /** Writes the navigation for the types contained by the given library. */
224 docLibraryNavigation(Library library) {
225 // Show the exception types separately.
226 final types = <Type>[];
227 final exceptions = <Type>[];
228
229 for (final type in orderByName(library.types)) {
230 if (type.isTop) continue;
231 if (type.name.startsWith('_')) continue;
232
233 if (type.name.endsWith('Exception')) {
234 exceptions.add(type);
235 } else {
236 types.add(type);
237 }
238 }
239
240 if ((types.length == 0) && (exceptions.length == 0)) return;
241
242 writeType(String icon, Type type) {
243 write('<li>');
244 if (_currentType == type) {
245 write(
246 '<div class="icon-$icon"></div><strong>${typeName(type)}</strong>');
247 } else {
248 write(a(typeUrl(type),
249 '<div class="icon-$icon"></div>${typeName(type)}'));
250 }
251 writeln('</li>');
252 }
253
254 writeln('<ul>');
255 types.forEach((type) => writeType(type.isClass ? 'class' : 'interface',
256 type));
257 exceptions.forEach((type) => writeType('exception', type));
258 writeln('</ul>');
259 }
260
261 docLibrary(Library library) {
262 _totalLibraries++;
263 _currentLibrary = library;
264 _currentType = null;
265
266 startFile(libraryUrl(library));
267 writeHeader(library.name);
268 writeln('<h1>Library <strong>${library.name}</strong></h1>');
269
270 // Look for a comment for the entire library.
271 final comment = _comments.findLibrary(library.baseSource);
272 if (comment != null) {
273 final html = md.markdownToHtml(comment);
274 writeln('<div class="doc">$html</div>');
275 }
276
277 // Document the top-level members.
278 docMembers(library.topType);
279
280 // Document the types.
281 final classes = <Type>[];
282 final interfaces = <Type>[];
283 final exceptions = <Type>[];
284
285 for (final type in orderByName(library.types)) {
286 if (type.isTop) continue;
287 if (type.name.startsWith('_')) continue;
288
289 if (type.name.endsWith('Exception')) {
290 exceptions.add(type);
291 } else if (type.isClass) {
292 classes.add(type);
293 } else {
294 interfaces.add(type);
295 }
296 }
297
298 docTypes(classes, 'Classes');
299 docTypes(interfaces, 'Interfaces');
300 docTypes(exceptions, 'Exceptions');
301
302 writeFooter();
303 endFile();
304
305 for (final type in library.types.getValues()) {
306 if (!type.isTop) docType(type);
307 }
308 }
309
310 docTypes(List<Type> types, String header) {
311 if (types.length == 0) return;
312
313 writeln('<h3>$header</h3>');
314
315 for (final type in types) {
316 writeln(
317 '''
318 <div class="type">
319 <h4>
320 ${a(typeUrl(type), "<strong>${typeName(type)}</strong>")}
321 </h4>
322 </div>
323 ''');
324 }
325 }
326
327 docType(Type type) {
328 _totalTypes++;
329 _currentType = type;
330
331 startFile(typeUrl(type));
332
333 final typeTitle =
334 '${type.isClass ? "Class" : "Interface"} ${typeName(type)}';
335 writeHeader('Library ${type.library.name} / $typeTitle');
336 writeln(
337 '''
338 <h1>${a(libraryUrl(type.library),
339 "Library <strong>${type.library.name}</strong>")}</h1>
340 <h2>${type.isClass ? "Class" : "Interface"}
341 <strong>${typeName(type, showBounds: true)}</strong></h2>
342 ''');
343
344 docInheritance(type);
345
346 docCode(type.span, getTypeComment(type));
347 docConstructors(type);
348 docMembers(type);
349
350 writeFooter();
351 endFile();
352 }
353
354 /** Document the superclass, superinterfaces and default class of [Type]. */
355 docInheritance(Type type) {
356 final isSubclass = (type.parent != null) && !type.parent.isObject;
357
358 Type defaultType;
359 if (type.definition is TypeDefinition) {
360 TypeDefinition definition = type.definition;
361 if (definition.defaultType != null) {
362 defaultType = definition.defaultType.type;
363 }
364 }
365
366 if (isSubclass ||
367 (type.interfaces != null && type.interfaces.length > 0) ||
368 (defaultType != null)) {
369 writeln('<p>');
370
371 if (isSubclass) {
372 write('Extends ${typeReference(type.parent)}. ');
373 }
374
375 if (type.interfaces != null && type.interfaces.length > 0) {
376 var interfaceStr = joinWithCommas(map(type.interfaces, typeReference));
377 write('Implements ${interfaceStr}. ');
378 }
379
380 if (defaultType != null) {
381 write('Has default class ${typeReference(defaultType)}.');
382 }
383 }
384 }
385
386 /** Document the constructors for [Type], if any. */
387 docConstructors(Type type) {
388 final names = type.constructors.getKeys().filter(
389 (name) => !name.startsWith('_'));
390
391 if (names.length > 0) {
392 writeln('<h3>Constructors</h3>');
393 names.sort((x, y) => x.toUpperCase().compareTo(y.toUpperCase()));
394
395 for (final name in names) {
396 docMethod(type, type.constructors[name], constructorName: name);
397 }
398 }
399 }
400
401 void docMembers(Type type) {
402 // Collect the different kinds of members.
403 final methods = [];
404 final fields = [];
405
406 for (final member in orderByName(type.members)) {
407 if (member.name.startsWith('_')) continue;
408
409 if (member.isProperty) {
410 if (member.canGet) methods.add(member.getter);
411 if (member.canSet) methods.add(member.setter);
412 } else if (member.isMethod) {
413 methods.add(member);
414 } else if (member.isField) {
415 fields.add(member);
416 }
417 }
418
419 if (methods.length > 0) {
420 writeln('<h3>Methods</h3>');
421 for (final method in methods) docMethod(type, method);
422 }
423
424 if (fields.length > 0) {
425 writeln('<h3>Fields</h3>');
426 for (final field in fields) docField(type, field);
427 }
428 }
429
430 /**
431 * Documents the [method] in type [type]. Handles all kinds of methods
432 * including getters, setters, and constructors.
433 */
434 docMethod(Type type, MethodMember method, [String constructorName = null]) {
435 _totalMembers++;
436 _currentMember = method;
437
438 writeln('<div class="method"><h4 id="${memberAnchor(method)}">');
439
440 if (includeSource) {
441 writeln('<span class="show-code">Code</span>');
442 }
443
444 if (method.isStatic && !type.isTop) {
445 write('static ');
446 }
447
448 if (method.isConstructor) {
449 write(method.isConst ? 'const ' : 'new ');
450 }
451
452 if (constructorName == null) {
453 annotateType(type, method.returnType);
454 }
455
456 // Translate specially-named methods: getters, setters, operators.
457 var name = method.name;
458 if (name.startsWith('get:')) {
459 // Getter.
460 name = 'get ${name.substring(4)}';
461 } else if (name.startsWith('set:')) {
462 // Setter.
463 name = 'set ${name.substring(4)}';
464 } else {
465 // See if it's an operator.
466 name = TokenKind.rawOperatorFromMethod(name);
467 if (name == null) {
468 name = method.name;
469 } else {
470 name = 'operator $name';
471 }
472 }
473
474 write('<strong>$name</strong>');
475
476 // Named constructors.
477 if (constructorName != null && constructorName != '') {
478 write('.');
479 write(constructorName);
480 }
481
482 docParamList(type, method);
483
484 write(''' <a class="anchor-link" href="#${memberAnchor(method)}"
485 title="Permalink to ${typeName(type)}.$name">#</a>''');
486 writeln('</h4>');
487
488 docCode(method.span, getMethodComment(method), showCode: true);
489
490 writeln('</div>');
491 }
492
493 /** Documents the field [field] of type [type]. */
494 docField(Type type, FieldMember field) {
495 _totalMembers++;
496 _currentMember = field;
497
498 writeln('<div class="field"><h4 id="${memberAnchor(field)}">');
499
500 if (includeSource) {
501 writeln('<span class="show-code">Code</span>');
502 }
503
504 if (field.isStatic && !type.isTop) {
505 write('static ');
506 }
507
508 if (field.isFinal) {
509 write('final ');
510 } else if (field.type.name == 'Dynamic') {
511 write('var ');
512 }
513
514 annotateType(type, field.type);
515 write(
516 '''
517 <strong>${field.name}</strong> <a class="anchor-link"
518 href="#${memberAnchor(field)}"
519 title="Permalink to ${typeName(type)}.${field.name}">#</a>
353 </h4> 520 </h4>
354 </div>
355 '''); 521 ''');
522
523 docCode(field.span, getFieldComment(field), showCode: true);
524 writeln('</div>');
525 }
526
527 docParamList(Type enclosingType, MethodMember member) {
528 write('(');
529 bool first = true;
530 bool inOptionals = false;
531 for (final parameter in member.parameters) {
532 if (!first) write(', ');
533
534 if (!inOptionals && parameter.isOptional) {
535 write('[');
536 inOptionals = true;
537 }
538
539 annotateType(enclosingType, parameter.type, parameter.name);
540
541 // Show the default value for named optional parameters.
542 if (parameter.isOptional && parameter.hasDefaultValue) {
543 write(' = ');
544 // TODO(rnystrom): Using the definition text here is a bit cheap.
545 // We really should be pretty-printing the AST so that if you have:
546 // foo([arg = 1 + /* comment */ 2])
547 // the docs should just show:
548 // foo([arg = 1 + 2])
549 // For now, we'll assume you don't do that.
550 write(parameter.definition.value.span.text);
551 }
552
553 first = false;
554 }
555
556 if (inOptionals) write(']');
557 write(')');
558 }
559
560 /**
561 * Documents the code contained within [span] with [comment]. If [showCode]
562 * is `true` (and [includeSource] is set), also includes the source code.
563 */
564 docCode(SourceSpan span, String comment, [bool showCode = false]) {
565 writeln('<div class="doc">');
566 if (comment != null) {
567 writeln(md.markdownToHtml(comment));
568 }
569
570 if (includeSource && showCode) {
571 writeln('<pre class="source">');
572 write(formatCode(span));
573 writeln('</pre>');
574 }
575
576 writeln('</div>');
577 }
578
579 /** Get the doc comment associated with the given type. */
580 String getTypeComment(Type type) => _comments.find(type.span);
581
582 /** Get the doc comment associated with the given method. */
583 String getMethodComment(MethodMember method) => _comments.find(method.span);
584
585 /** Get the doc comment associated with the given field. */
586 String getFieldComment(FieldMember field) => _comments.find(field.span);
587
588 /**
589 * Creates a hyperlink. Handles turning the [href] into an appropriate
590 * relative path from the current file.
591 */
592 String a(String href, String contents, [String class]) {
593 final css = class == null ? '' : ' class="$class"';
594 return '<a href="${relativePath(href)}"$css>$contents</a>';
595 }
596
597 /**
598 * Writes a type annotation for the given type and (optional) parameter name.
599 */
600 annotateType(Type enclosingType, Type type, [String paramName = null]) {
601 // Don't bother explicitly displaying Dynamic.
602 if (type.isVar) {
603 if (paramName !== null) write(paramName);
604 return;
605 }
606
607 // For parameters, handle non-typedefed function types.
608 if (paramName !== null) {
609 final call = type.getCallMethod();
610 if (call != null) {
611 annotateType(enclosingType, call.returnType);
612 write(paramName);
613
614 docParamList(enclosingType, call);
615 return;
616 }
617 }
618
619 linkToType(enclosingType, type);
620
621 write(' ');
622 if (paramName !== null) write(paramName);
623 }
624
625 /** Writes a link to a human-friendly string representation for a type. */
626 linkToType(Type enclosingType, Type type) {
627 if (type is ParameterType) {
628 // If we're using a type parameter within the body of a generic class then
629 // just link back up to the class.
630 write(a(typeUrl(enclosingType), type.name));
631 return;
632 }
633
634 // Link to the type.
635 // Use .genericType to avoid writing the <...> here.
636 write(a(typeUrl(type), type.genericType.name));
637
638 // See if it's a generic type.
639 if (type.isGeneric) {
640 // TODO(rnystrom): This relies on a weird corner case of frog. Currently,
641 // the only time we get into this case is when we have a "raw" generic
642 // that's been instantiated with Dynamic for all type arguments. It's kind
643 // of strange that frog works that way, but we take advantage of it to
644 // show raw types without any type arguments.
645 return;
646 }
647
648 // See if it's an instantiation of a generic type.
649 final typeArgs = type.typeArgsInOrder;
650 if (typeArgs != null) {
651 write('&lt;');
652 bool first = true;
653 for (final arg in typeArgs) {
654 if (!first) write(', ');
655 first = false;
656 linkToType(enclosingType, arg);
657 }
658 write('&gt;');
659 }
660 }
661
662 /** Creates a linked cross reference to [type]. */
663 typeReference(Type type) {
664 // TODO(rnystrom): Do we need to handle ParameterTypes here like
665 // annotation() does?
666 return a(typeUrl(type), typeName(type), class: 'crossref');
667 }
668
669 /** Generates a human-friendly string representation for a type. */
670 typeName(Type type, [bool showBounds = false]) {
671 // See if it's a generic type.
672 if (type.isGeneric) {
673 final typeParams = [];
674 for (final typeParam in type.genericType.typeParameters) {
675 if (showBounds &&
676 (typeParam.extendsType != null) &&
677 !typeParam.extendsType.isObject) {
678 final bound = typeName(typeParam.extendsType, showBounds: true);
679 typeParams.add('${typeParam.name} extends $bound');
680 } else {
681 typeParams.add(typeParam.name);
682 }
683 }
684
685 final params = Strings.join(typeParams, ', ');
686 return '${type.name}&lt;$params&gt;';
687 }
688
689 // See if it's an instantiation of a generic type.
690 final typeArgs = type.typeArgsInOrder;
691 if (typeArgs != null) {
692 final args = Strings.join(map(typeArgs, (arg) => typeName(arg)), ', ');
693 return '${type.genericType.name}&lt;$args&gt;';
694 }
695
696 // Regular type.
697 return type.name;
698 }
699
700 /**
701 * Takes a string of Dart code and turns it into sanitized HTML.
702 */
703 formatCode(SourceSpan span) {
704 // Remove leading indentation to line up with first line.
705 final column = getSpanColumn(span);
706 final lines = span.text.split('\n');
707 // TODO(rnystrom): Dirty hack.
708 for (final i = 1; i < lines.length; i++) {
709 lines[i] = unindent(lines[i], column);
710 }
711
712 final code = Strings.join(lines, '\n');
713
714 // Syntax highlight.
715 return classifySource(new SourceFile('', code));
716 }
717
718 /**
719 * This will be called whenever a doc comment hits a `[name]` in square
720 * brackets. It will try to figure out what the name refers to and link or
721 * style it appropriately.
722 */
723 md.Node resolveNameReference(String name) {
724 makeLink(String href) {
725 final anchor = new md.Element.text('a', name);
726 anchor.attributes['href'] = relativePath(href);
727 anchor.attributes['class'] = 'crossref';
728 return anchor;
729 }
730
731 findMember(Type type) {
732 final member = type.members[name];
733 if (member == null) return null;
734
735 // Special case: if the member we've resolved is a property (i.e. it wraps
736 // a getter and/or setter then *that* member itself won't be on the docs,
737 // just the getter or setter will be. So pick one of those to link to.
738 if (member.isProperty) {
739 return member.canGet ? member.getter : member.setter;
740 }
741
742 return member;
743 }
744
745 // See if it's a parameter of the current method.
746 if (_currentMember != null) {
747 for (final parameter in _currentMember.parameters) {
748 if (parameter.name == name) {
749 final element = new md.Element.text('span', name);
750 element.attributes['class'] = 'param';
751 return element;
752 }
753 }
754 }
755
756 // See if it's another member of the current type.
757 if (_currentType != null) {
758 final member = findMember(_currentType);
759 if (member != null) {
760 return makeLink(memberUrl(member));
761 }
762 }
763
764 // See if it's another type in the current library.
765 if (_currentLibrary != null) {
766 final type = _currentLibrary.types[name];
767 if (type != null) {
768 return makeLink(typeUrl(type));
769 }
770
771 // See if it's a top-level member in the current library.
772 final member = findMember(_currentLibrary.topType);
773 if (member != null) {
774 return makeLink(memberUrl(member));
775 }
776 }
777
778 // TODO(rnystrom): Should also consider:
779 // * Names imported by libraries this library imports.
780 // * Type parameters of the enclosing type.
781
782 return new md.Element.text('code', name);
783 }
784
785 // TODO(rnystrom): Move into SourceSpan?
786 int getSpanColumn(SourceSpan span) {
787 final line = span.file.getLine(span.start);
788 return span.file.getColumn(line, span.start);
356 } 789 }
357 } 790 }
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
« no previous file with comments | « utils/dartdoc/comment_map.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698