| OLD | NEW |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /** An awesome documentation generator. */ | 5 /** An awesome documentation generator. */ |
| 6 #library('dartdoc'); | 6 #library('dartdoc'); |
| 7 | 7 |
| 8 #import('../../frog/lang.dart'); | 8 #import('../../frog/lang.dart'); |
| 9 #import('../../frog/file_system.dart'); |
| 9 #import('../../frog/file_system_node.dart'); | 10 #import('../../frog/file_system_node.dart'); |
| 10 | 11 |
| 11 #source('classify.dart'); | 12 #source('classify.dart'); |
| 12 | 13 |
| 13 /** Path to corePath library. */ | 14 /** Path to corePath library. */ |
| 14 final corePath = 'lib'; | 15 final corePath = 'lib'; |
| 15 | 16 |
| 16 /** Path to generate html files into. */ | 17 /** Path to generate html files into. */ |
| 17 final outdir = 'docs'; | 18 final outdir = 'docs'; |
| 18 | 19 |
| 19 /** Special comment position used to store the library-level doc comment. */ | 20 /** Special comment position used to store the library-level doc comment. */ |
| 20 final _libraryDoc = -1; | 21 final _libraryDoc = -1; |
| 21 | 22 |
| 22 /** The file currently being written to. */ | 23 /** The file currently being written to. */ |
| 23 StringBuffer _file; | 24 StringBuffer _file; |
| 24 | 25 |
| 25 /** | 26 /** |
| 26 * The cached lookup-table to associate doc comments with spans. The outer map | 27 * The cached lookup-table to associate doc comments with spans. The outer map |
| 27 * is from filenames to doc comments in that file. The inner map maps from the | 28 * is from filenames to doc comments in that file. The inner map maps from the |
| 28 * token positions to doc comments. Each position is the starting offset of the | 29 * token positions to doc comments. Each position is the starting offset of the |
| 29 * next non-comment token *following* the doc comment. For example, the position | 30 * next non-comment token *following* the doc comment. For example, the position |
| 30 * for this comment would be the position of the "Map" token below. | 31 * for this comment would be the position of the "Map" token below. |
| 31 */ | 32 */ |
| 32 Map<String, Map<int, String>> _comments; | 33 Map<String, Map<int, String>> _comments; |
| 33 | 34 |
| 34 int _totalLibraries = 0; | 35 int _totalLibraries = 0; |
| 35 int _totalTypes = 0; | 36 int _totalTypes = 0; |
| 36 int _totalMembers = 0; | 37 int _totalMembers = 0; |
| 37 | 38 |
| 39 FileSystem files; |
| 40 |
| 38 /** | 41 /** |
| 39 * Run this from the frog/samples directory. Before running, you need | 42 * Run this from the frog/samples directory. Before running, you need |
| 40 * to create a docs dir with 'mkdir docs' - since Dart currently doesn't | 43 * to create a docs dir with 'mkdir docs' - since Dart currently doesn't |
| 41 * support creating new directories. | 44 * support creating new directories. |
| 42 */ | 45 */ |
| 43 void main() { | 46 void main() { |
| 44 // The entrypoint of the library to generate docs for. | 47 // The entrypoint of the library to generate docs for. |
| 45 final libPath = process.argv[2]; | 48 final libPath = process.argv[2]; |
| 46 | 49 |
| 47 // TODO(rnystrom): Get options and homedir like frog.dart does. | 50 files = new NodeFileSystem(); |
| 48 final files = new NodeFileSystem(); | |
| 49 parseOptions('../../frog', [] /* args */, files); | 51 parseOptions('../../frog', [] /* args */, files); |
| 50 | 52 |
| 51 final elapsed = time(() { | 53 final elapsed = time(() { |
| 54 _comments = <String, Map<int, String>>{}; |
| 55 |
| 52 initializeWorld(files); | 56 initializeWorld(files); |
| 53 | 57 |
| 54 world.processScript(libPath); | 58 world.processScript(libPath); |
| 55 world.resolveAll(); | 59 world.resolveAll(); |
| 56 | 60 |
| 57 _comments = <String, Map<int, String>>{}; | 61 // Clean the output directory. |
| 62 if (files.fileExists(outdir)) { |
| 63 files.removeDirectory(outdir, recursive: true); |
| 64 } |
| 65 files.createDirectory(outdir, recursive: true); |
| 58 | 66 |
| 59 for (var library in world.libraries.getValues()) { | 67 // Copy over the static files. |
| 68 for (final file in ['interact.js', 'styles.css']) { |
| 69 copyStatic(file); |
| 70 } |
| 71 |
| 72 // Generate the docs. |
| 73 for (final library in world.libraries.getValues()) { |
| 60 docLibrary(library); | 74 docLibrary(library); |
| 61 } | 75 } |
| 62 | 76 |
| 63 docIndex(world.libraries.getValues()); | 77 docIndex(world.libraries.getValues()); |
| 64 }); | 78 }); |
| 65 | 79 |
| 66 print('Documented $_totalLibraries libraries, $_totalTypes types, and ' + | 80 print('Documented $_totalLibraries libraries, $_totalTypes types, and ' + |
| 67 '$_totalMembers members in ${elapsed}msec.'); | 81 '$_totalMembers members in ${elapsed}msec.'); |
| 68 } | 82 } |
| 69 | 83 |
| 84 /** Copies the static file at 'static/file' to the output directory. */ |
| 85 copyStatic(String file) { |
| 86 var contents = files.readAll(joinPaths('static', file)); |
| 87 files.writeString(joinPaths(outdir, file), contents); |
| 88 } |
| 89 |
| 70 num time(callback()) { | 90 num time(callback()) { |
| 71 // Unlike world.withTiming, returns the elapsed time. | 91 // Unlike world.withTiming, returns the elapsed time. |
| 72 final watch = new Stopwatch(); | 92 final watch = new Stopwatch(); |
| 73 watch.start(); | 93 watch.start(); |
| 74 callback(); | 94 callback(); |
| 75 watch.stop(); | 95 watch.stop(); |
| 76 return watch.elapsedInMs(); | 96 return watch.elapsedInMs(); |
| 77 } | 97 } |
| 78 | 98 |
| 79 startFile() { | 99 startFile() { |
| (...skipping 24 matching lines...) Expand all Loading... |
| 104 ''' | 124 ''' |
| 105 <html><head> | 125 <html><head> |
| 106 <title>Index</title> | 126 <title>Index</title> |
| 107 <link rel="stylesheet" type="text/css" href="styles.css" /> | 127 <link rel="stylesheet" type="text/css" href="styles.css" /> |
| 108 </head> | 128 </head> |
| 109 <body> | 129 <body> |
| 110 <div class="content"> | 130 <div class="content"> |
| 111 <ul> | 131 <ul> |
| 112 '''); | 132 '''); |
| 113 | 133 |
| 114 var sorted = new List<Library>.from(libraries); | 134 final sorted = new List<Library>.from(libraries); |
| 115 sorted.sort((a, b) => a.name.compareTo(b.name)); | 135 sorted.sort((a, b) => a.name.compareTo(b.name)); |
| 116 | 136 |
| 117 for (var library in sorted) { | 137 for (final library in sorted) { |
| 118 writeln( | 138 writeln( |
| 119 ''' | 139 ''' |
| 120 <li><a href="${sanitize(library.name)}.html"> | 140 <li><a href="${sanitize(library.name)}.html"> |
| 121 Library ${library.name}</a> | 141 Library ${library.name}</a> |
| 122 </li> | 142 </li> |
| 123 '''); | 143 '''); |
| 124 } | 144 } |
| 125 | 145 |
| 126 writeln( | 146 writeln( |
| 127 ''' | 147 ''' |
| (...skipping 25 matching lines...) Expand all Loading... |
| 153 | 173 |
| 154 bool needsSeparator = false; | 174 bool needsSeparator = false; |
| 155 | 175 |
| 156 // Look for a comment for the entire library. | 176 // Look for a comment for the entire library. |
| 157 final comment = findCommentInFile(library.baseSource, _libraryDoc); | 177 final comment = findCommentInFile(library.baseSource, _libraryDoc); |
| 158 if (comment != null) { | 178 if (comment != null) { |
| 159 writeln('<div class="doc"><p>$comment</p></div>'); | 179 writeln('<div class="doc"><p>$comment</p></div>'); |
| 160 needsSeparator = true; | 180 needsSeparator = true; |
| 161 } | 181 } |
| 162 | 182 |
| 163 for (var type in library.types.getValues()) { | 183 for (final type in library.types.getValues()) { |
| 164 if (needsSeparator) writeln('<hr/>'); | 184 if (needsSeparator) writeln('<hr/>'); |
| 165 if (docType(type)) needsSeparator = true; | 185 if (docType(type)) needsSeparator = true; |
| 166 } | 186 } |
| 167 | 187 |
| 168 writeln( | 188 writeln( |
| 169 ''' | 189 ''' |
| 170 </div> | 190 </div> |
| 171 </body></html> | 191 </body></html> |
| 172 '''); | 192 '''); |
| 173 | 193 |
| (...skipping 20 matching lines...) Expand all Loading... |
| 194 '''); | 214 '''); |
| 195 | 215 |
| 196 docInheritance(type); | 216 docInheritance(type); |
| 197 docCode(type.span); | 217 docCode(type.span); |
| 198 docConstructors(type); | 218 docConstructors(type); |
| 199 | 219 |
| 200 wroteSomething = true; | 220 wroteSomething = true; |
| 201 } | 221 } |
| 202 | 222 |
| 203 // Collect the different kinds of members. | 223 // Collect the different kinds of members. |
| 204 var methods = []; | 224 final methods = []; |
| 205 var fields = []; | 225 final fields = []; |
| 206 | 226 |
| 207 for (var member in orderValuesByKeys(type.members)) { | 227 for (final member in orderValuesByKeys(type.members)) { |
| 208 if (member.isMethod && | 228 if (member.isMethod && |
| 209 (member.definition != null) && | 229 (member.definition != null) && |
| 210 !member.name.startsWith('_')) { | 230 !member.name.startsWith('_')) { |
| 211 methods.add(member); | 231 methods.add(member); |
| 212 } else if (member.isProperty) { | 232 } else if (member.isProperty) { |
| 213 if (member.canGet) methods.add(member.getter); | 233 if (member.canGet) methods.add(member.getter); |
| 214 if (member.canSet) methods.add(member.setter); | 234 if (member.canSet) methods.add(member.setter); |
| 215 } else if (member.isField && !member.name.startsWith('_')) { | 235 } else if (member.isField && !member.name.startsWith('_')) { |
| 216 fields.add(member); | 236 fields.add(member); |
| 217 } | 237 } |
| 218 } | 238 } |
| 219 | 239 |
| 220 if (methods.length > 0) { | 240 if (methods.length > 0) { |
| 221 writeln('<h3>Methods</h3>'); | 241 writeln('<h3>Methods</h3>'); |
| 222 for (var method in methods) docMethod(type.name, method); | 242 for (final method in methods) docMethod(type.name, method); |
| 223 } | 243 } |
| 224 | 244 |
| 225 if (fields.length > 0) { | 245 if (fields.length > 0) { |
| 226 writeln('<h3>Fields</h3>'); | 246 writeln('<h3>Fields</h3>'); |
| 227 for (var field in fields) docField(type.name, field); | 247 for (final field in fields) docField(type.name, field); |
| 228 } | 248 } |
| 229 | 249 |
| 230 return wroteSomething || methods.length > 0 || fields.length > 0; | 250 return wroteSomething || methods.length > 0 || fields.length > 0; |
| 231 } | 251 } |
| 232 | 252 |
| 233 /** Document the superclass and superinterfaces of [Type]. */ | 253 /** Document the superclass and superinterfaces of [Type]. */ |
| 234 docInheritance(Type type) { | 254 docInheritance(Type type) { |
| 235 // Show the superclass and superinterface(s). | 255 // Show the superclass and superinterface(s). |
| 236 if ((type.parent != null) && (type.parent.isObject) || | 256 if ((type.parent != null) && (type.parent.isObject) || |
| 237 (type.interfaces != null && type.interfaces.length > 0)) { | 257 (type.interfaces != null && type.interfaces.length > 0)) { |
| 238 writeln('<p>'); | 258 writeln('<p>'); |
| 239 | 259 |
| 240 if (type.parent != null) { | 260 if (type.parent != null) { |
| 241 write('Extends ${typeRef(type.parent)}. '); | 261 write('Extends ${typeRef(type.parent)}. '); |
| 242 } | 262 } |
| 243 | 263 |
| 244 if (type.interfaces != null) { | 264 if (type.interfaces != null) { |
| 245 var interfaces = []; | 265 final interfaces = []; |
| 246 switch (type.interfaces.length) { | 266 switch (type.interfaces.length) { |
| 247 case 0: | 267 case 0: |
| 248 // Do nothing. | 268 // Do nothing. |
| 249 break; | 269 break; |
| 250 | 270 |
| 251 case 1: | 271 case 1: |
| 252 write('Implements ${typeRef(type.interfaces[0])}.'); | 272 write('Implements ${typeRef(type.interfaces[0])}.'); |
| 253 break; | 273 break; |
| 254 | 274 |
| 255 case 2: | 275 case 2: |
| 256 write('''Implements ${typeRef(type.interfaces[0])} and | 276 write('''Implements ${typeRef(type.interfaces[0])} and |
| 257 ${typeRef(type.interfaces[1])}.'''); | 277 ${typeRef(type.interfaces[1])}.'''); |
| 258 break; | 278 break; |
| 259 | 279 |
| 260 default: | 280 default: |
| 261 write('Implements '); | 281 write('Implements '); |
| 262 for (var i = 0; i < type.interfaces.length; i++) { | 282 for (final i = 0; i < type.interfaces.length; i++) { |
| 263 write('${typeRef(type.interfaces[i])}'); | 283 write('${typeRef(type.interfaces[i])}'); |
| 264 if (i < type.interfaces.length - 1) { | 284 if (i < type.interfaces.length - 1) { |
| 265 write(', '); | 285 write(', '); |
| 266 } else { | 286 } else { |
| 267 write(' and '); | 287 write(' and '); |
| 268 } | 288 } |
| 269 } | 289 } |
| 270 write('.'); | 290 write('.'); |
| 271 break; | 291 break; |
| 272 } | 292 } |
| 273 } | 293 } |
| 274 } | 294 } |
| 275 } | 295 } |
| 276 | 296 |
| 277 /** Document the constructors for [Type], if any. */ | 297 /** Document the constructors for [Type], if any. */ |
| 278 docConstructors(Type type) { | 298 docConstructors(Type type) { |
| 279 if (type.constructors.length > 0) { | 299 if (type.constructors.length > 0) { |
| 280 writeln('<h3>Constructors</h3>'); | 300 writeln('<h3>Constructors</h3>'); |
| 281 for (var name in type.constructors.getKeys()) { | 301 for (final name in type.constructors.getKeys()) { |
| 282 var constructor = type.constructors[name]; | 302 final constructor = type.constructors[name]; |
| 283 docMethod(type.name, constructor, namedConstructor: name); | 303 docMethod(type.name, constructor, namedConstructor: name); |
| 284 } | 304 } |
| 285 } | 305 } |
| 286 } | 306 } |
| 287 | 307 |
| 288 /** | 308 /** |
| 289 * Documents the [method] in a type named [typeName]. Handles all kinds of | 309 * Documents the [method] in a type named [typeName]. Handles all kinds of |
| 290 * methods including getters, setters, and constructors. | 310 * methods including getters, setters, and constructors. |
| 291 */ | 311 */ |
| 292 docMethod(String typeName, MethodMember method, | 312 docMethod(String typeName, MethodMember method, |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 333 | 353 |
| 334 write('<strong>$name</strong>'); | 354 write('<strong>$name</strong>'); |
| 335 | 355 |
| 336 // Named constructors. | 356 // Named constructors. |
| 337 if (namedConstructor != null && namedConstructor != '') { | 357 if (namedConstructor != null && namedConstructor != '') { |
| 338 write('.'); | 358 write('.'); |
| 339 write(namedConstructor); | 359 write(namedConstructor); |
| 340 } | 360 } |
| 341 | 361 |
| 342 write('('); | 362 write('('); |
| 343 var paramList = []; | 363 final paramList = []; |
| 344 if (method.parameters == null) print(method.name); | 364 if (method.parameters == null) print(method.name); |
| 345 for (var p in method.parameters) { | 365 for (final p in method.parameters) { |
| 346 paramList.add('${optionalTypeRef(p.type)}${p.name}'); | 366 paramList.add('${optionalTypeRef(p.type)}${p.name}'); |
| 347 } | 367 } |
| 348 write(Strings.join(paramList, ", ")); | 368 write(Strings.join(paramList, ", ")); |
| 349 write(')'); | 369 write(')'); |
| 350 | 370 |
| 351 write(''' <a class="anchor-link" href="#$typeName.${method.name}" | 371 write(''' <a class="anchor-link" href="#$typeName.${method.name}" |
| 352 title="Permalink to $typeName.$name">#</a>'''); | 372 title="Permalink to $typeName.$name">#</a>'''); |
| 353 writeln('</h4>'); | 373 writeln('</h4>'); |
| 354 | 374 |
| 355 docCode(method.span, showCode: true); | 375 docCode(method.span, showCode: true); |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 391 docCode(field.span, showCode: true); | 411 docCode(field.span, showCode: true); |
| 392 writeln('</div>'); | 412 writeln('</div>'); |
| 393 } | 413 } |
| 394 | 414 |
| 395 /** | 415 /** |
| 396 * Writes a type annotation for [type]. Will hyperlink it to that type's | 416 * Writes a type annotation for [type]. Will hyperlink it to that type's |
| 397 * documentation if possible. | 417 * documentation if possible. |
| 398 */ | 418 */ |
| 399 typeRef(Type type) { | 419 typeRef(Type type) { |
| 400 if (type.library != null) { | 420 if (type.library != null) { |
| 401 var library = sanitize(type.library.name); | 421 final library = sanitize(type.library.name); |
| 402 return '<a href="${library}.html#${type.name}">${type.name}</a>'; | 422 return '<a href="${library}.html#${type.name}">${type.name}</a>'; |
| 403 } else { | 423 } else { |
| 404 return type.name; | 424 return type.name; |
| 405 } | 425 } |
| 406 } | 426 } |
| 407 | 427 |
| 408 /** | 428 /** |
| 409 * Creates a linked string for an optional type annotation. Returns an empty | 429 * Creates a linked string for an optional type annotation. Returns an empty |
| 410 * string if the type is Dynamic. | 430 * string if the type is Dynamic. |
| 411 */ | 431 */ |
| 412 optionalTypeRef(Type type) { | 432 optionalTypeRef(Type type) { |
| 413 if (type.name == 'Dynamic') { | 433 if (type.name == 'Dynamic') { |
| 414 return ''; | 434 return ''; |
| 415 } else { | 435 } else { |
| 416 return typeRef(type) + ' '; | 436 return typeRef(type) + ' '; |
| 417 } | 437 } |
| 418 } | 438 } |
| 419 | 439 |
| 420 /** | 440 /** |
| 421 * Documents the code contained within [span]. Will include the previous | 441 * Documents the code contained within [span]. Will include the previous |
| 422 * Dartdoc associated with that span if found, and will include the syntax | 442 * Dartdoc associated with that span if found, and will include the syntax |
| 423 * highlighted code itself if desired. | 443 * highlighted code itself if desired. |
| 424 */ | 444 */ |
| 425 docCode(SourceSpan span, [bool showCode = false]) { | 445 docCode(SourceSpan span, [bool showCode = false]) { |
| 426 if (span == null) return; | 446 if (span == null) return; |
| 427 | 447 |
| 428 writeln('<div class="doc">'); | 448 writeln('<div class="doc">'); |
| 429 var comment = findComment(span); | 449 final comment = findComment(span); |
| 430 if (comment != null) { | 450 if (comment != null) { |
| 431 writeln('<p>$comment</p>'); | 451 writeln('<p>$comment</p>'); |
| 432 } | 452 } |
| 433 | 453 |
| 434 if (showCode) { | 454 if (showCode) { |
| 435 writeln('<pre class="source">'); | 455 writeln('<pre class="source">'); |
| 436 write(formatCode(span)); | 456 write(formatCode(span)); |
| 437 writeln('</pre>'); | 457 writeln('</pre>'); |
| 438 } | 458 } |
| 439 | 459 |
| 440 writeln('</div>'); | 460 writeln('</div>'); |
| 441 } | 461 } |
| 442 | 462 |
| 443 /** Finds the doc comment preceding the given source span, if there is one. */ | 463 /** Finds the doc comment preceding the given source span, if there is one. */ |
| 444 findComment(SourceSpan span) => findCommentInFile(span.file, span.start); | 464 findComment(SourceSpan span) => findCommentInFile(span.file, span.start); |
| 445 | 465 |
| 446 /** Finds the doc comment preceding the given source span, if there is one. */ | 466 /** Finds the doc comment preceding the given source span, if there is one. */ |
| 447 findCommentInFile(SourceFile file, int position) { | 467 findCommentInFile(SourceFile file, int position) { |
| 448 // Get the doc comments for this file. | 468 // Get the doc comments for this file. |
| 449 var fileComments = _comments.putIfAbsent(file.filename, | 469 final fileComments = _comments.putIfAbsent(file.filename, |
| 450 () => parseDocComments(file)); | 470 () => parseDocComments(file)); |
| 451 | 471 |
| 452 return fileComments[position]; | 472 return fileComments[position]; |
| 453 } | 473 } |
| 454 | 474 |
| 455 parseDocComments(SourceFile file) { | 475 parseDocComments(SourceFile file) { |
| 456 var comments = <int, String>{}; | 476 final comments = <int, String>{}; |
| 457 | 477 |
| 458 var tokenizer = new Tokenizer(file, false); | 478 final tokenizer = new Tokenizer(file, false); |
| 459 var lastComment = null; | 479 var lastComment = null; |
| 460 | 480 |
| 461 while (true) { | 481 while (true) { |
| 462 var token = tokenizer.next(); | 482 final token = tokenizer.next(); |
| 463 if (token.kind == TokenKind.END_OF_FILE) break; | 483 if (token.kind == TokenKind.END_OF_FILE) break; |
| 464 | 484 |
| 465 if (token.kind == TokenKind.COMMENT) { | 485 if (token.kind == TokenKind.COMMENT) { |
| 466 var text = token.text; | 486 final text = token.text; |
| 467 if (text.startsWith('/**')) { | 487 if (text.startsWith('/**')) { |
| 468 // Remember that we've encountered a doc comment. | 488 // Remember that we've encountered a doc comment. |
| 469 lastComment = stripComment(token.text); | 489 lastComment = stripComment(token.text); |
| 470 } | 490 } |
| 471 } else if (token.kind == TokenKind.WHITESPACE) { | 491 } else if (token.kind == TokenKind.WHITESPACE) { |
| 472 // Ignore whitespace tokens. | 492 // Ignore whitespace tokens. |
| 473 } else if (token.kind == TokenKind.HASH) { | 493 } else if (token.kind == TokenKind.HASH) { |
| 474 // Look for #library() to find the library comment. | 494 // Look for #library() to find the library comment. |
| 475 var next = tokenizer.next(); | 495 final next = tokenizer.next(); |
| 476 if ((lastComment != null) && (next.kind == TokenKind.LIBRARY)) { | 496 if ((lastComment != null) && (next.kind == TokenKind.LIBRARY)) { |
| 477 comments[_libraryDoc] = lastComment; | 497 comments[_libraryDoc] = lastComment; |
| 478 lastComment = null; | 498 lastComment = null; |
| 479 } | 499 } |
| 480 } else { | 500 } else { |
| 481 if (lastComment != null) { | 501 if (lastComment != null) { |
| 482 // We haven't attached the last doc comment to something yet, so stick | 502 // We haven't attached the last doc comment to something yet, so stick |
| 483 // it to this token. | 503 // it to this token. |
| 484 comments[token.start] = lastComment; | 504 comments[token.start] = lastComment; |
| 485 lastComment = null; | 505 lastComment = null; |
| 486 } | 506 } |
| 487 } | 507 } |
| 488 } | 508 } |
| 489 | 509 |
| 490 return comments; | 510 return comments; |
| 491 } | 511 } |
| 492 | 512 |
| 493 /** | 513 /** |
| 494 * Takes a string of Dart code and turns it into sanitized HTML. | 514 * Takes a string of Dart code and turns it into sanitized HTML. |
| 495 */ | 515 */ |
| 496 formatCode(SourceSpan span) { | 516 formatCode(SourceSpan span) { |
| 497 // Remove leading indentation to line up with first line. | 517 // Remove leading indentation to line up with first line. |
| 498 var column = getSpanColumn(span); | 518 final column = getSpanColumn(span); |
| 499 var lines = span.text.split('\n'); | 519 final lines = span.text.split('\n'); |
| 500 // TODO(rnystrom): Dirty hack. | 520 // TODO(rnystrom): Dirty hack. |
| 501 for (int i = 1; i < lines.length; i++) { | 521 for (final i = 1; i < lines.length; i++) { |
| 502 lines[i] = unindent(lines[i], column); | 522 lines[i] = unindent(lines[i], column); |
| 503 } | 523 } |
| 504 | 524 |
| 505 var code = Strings.join(lines, '\n'); | 525 final code = Strings.join(lines, '\n'); |
| 506 | 526 |
| 507 // Syntax highlight. | 527 // Syntax highlight. |
| 508 return classifySource(new SourceFile('', code)); | 528 return classifySource(new SourceFile('', code)); |
| 509 } | 529 } |
| 510 | 530 |
| 511 // TODO(rnystrom): Move into SourceSpan? | 531 // TODO(rnystrom): Move into SourceSpan? |
| 512 int getSpanColumn(SourceSpan span) { | 532 int getSpanColumn(SourceSpan span) { |
| 513 var line = span.file.getLine(span.start); | 533 final line = span.file.getLine(span.start); |
| 514 return span.file.getColumn(line, span.start); | 534 return span.file.getColumn(line, span.start); |
| 515 } | 535 } |
| 516 | 536 |
| 517 /** Removes up to [indentation] leading whitespace characters from [text]. */ | 537 /** Removes up to [indentation] leading whitespace characters from [text]. */ |
| 518 unindent(String text, int indentation) { | 538 unindent(String text, int indentation) { |
| 519 var start; | 539 var start; |
| 520 for (start = 0; start < Math.min(indentation, text.length); start++) { | 540 for (start = 0; start < Math.min(indentation, text.length); start++) { |
| 521 // Stop if we hit a non-whitespace character. | 541 // Stop if we hit a non-whitespace character. |
| 522 if (text[start] != ' ') break; | 542 if (text[start] != ' ') break; |
| 523 } | 543 } |
| 524 | 544 |
| 525 return text.substring(start); | 545 return text.substring(start); |
| 526 } | 546 } |
| 527 | 547 |
| 528 /** | 548 /** |
| 529 * Pulls the raw text out of a doc comment (i.e. removes the comment | 549 * Pulls the raw text out of a doc comment (i.e. removes the comment |
| 530 * characters. | 550 * characters. |
| 531 */ | 551 */ |
| 532 // TODO(rnystrom): Should handle [name] and [:code:] in comments. Should also | 552 // TODO(rnystrom): Should handle [name] and [:code:] in comments. Should also |
| 533 // break empty lines into multiple paragraphs. Other formatting? | 553 // break empty lines into multiple paragraphs. Other formatting? |
| 534 // See dart/compiler/java/com/google/dart/compiler/backend/doc for ideas. | 554 // See dart/compiler/java/com/google/dart/compiler/backend/doc for ideas. |
| 535 // (/DartDocumentationVisitor.java#180) | 555 // (/DartDocumentationVisitor.java#180) |
| 536 stripComment(comment) { | 556 stripComment(comment) { |
| 537 StringBuffer buf = new StringBuffer(); | 557 StringBuffer buf = new StringBuffer(); |
| 538 | 558 |
| 539 for (var line in comment.split('\n')) { | 559 for (final line in comment.split('\n')) { |
| 540 line = line.trim(); | 560 line = line.trim(); |
| 541 if (line.startsWith('/**')) line = line.substring(3, line.length); | 561 if (line.startsWith('/**')) line = line.substring(3, line.length); |
| 542 if (line.endsWith('*/')) line = line.substring(0, line.length-2); | 562 if (line.endsWith('*/')) line = line.substring(0, line.length-2); |
| 543 line = line.trim(); | 563 line = line.trim(); |
| 544 while (line.startsWith('*')) line = line.substring(1, line.length); | 564 while (line.startsWith('*')) line = line.substring(1, line.length); |
| 545 line = line.trim(); | 565 line = line.trim(); |
| 546 buf.add(line); | 566 buf.add(line); |
| 547 buf.add(' '); | 567 buf.add(' '); |
| 548 } | 568 } |
| 549 | 569 |
| 550 return buf.toString(); | 570 return buf.toString(); |
| 551 } | 571 } |
| OLD | NEW |