| 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 /** | 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('files.dart'); |
| 25 #source('utils.dart'); |
| 24 | 26 |
| 25 /** Path to corePath library. */ | 27 /** Path to corePath library. */ |
| 26 final corePath = 'lib'; | 28 final corePath = 'lib'; |
| 27 | 29 |
| 28 /** Path to generate html files into. */ | 30 /** Path to generate html files into. */ |
| 29 final outdir = 'docs'; | 31 final outdir = 'docs'; |
| 30 | 32 |
| 31 /** Set to `true` to include the source code in the generated docs. */ | 33 /** Set to `true` to include the source code in the generated docs. */ |
| 32 bool includeSource = true; | 34 bool includeSource = false; |
| 33 | 35 |
| 34 /** Special comment position used to store the library-level doc comment. */ | 36 /** Special comment position used to store the library-level doc comment. */ |
| 35 final _libraryDoc = -1; | 37 final _libraryDoc = -1; |
| 36 | 38 |
| 37 /** The path to the file currently being written to, relative to [outdir]. */ | |
| 38 String _filePath; | |
| 39 | |
| 40 /** The file currently being written to. */ | |
| 41 StringBuffer _file; | |
| 42 | |
| 43 /** The library that we're currently generating docs for. */ | 39 /** The library that we're currently generating docs for. */ |
| 44 Library _currentLibrary; | 40 Library _currentLibrary; |
| 45 | 41 |
| 46 /** The type that we're currently generating docs for. */ | 42 /** The type that we're currently generating docs for. */ |
| 47 Type _currentType; | 43 Type _currentType; |
| 48 | 44 |
| 49 /** The member that we're currently generating docs for. */ | 45 /** The member that we're currently generating docs for. */ |
| 50 Member _currentMember; | 46 Member _currentMember; |
| 51 | 47 |
| 52 /** | 48 /** |
| 53 * The cached lookup-table to associate doc comments with spans. The outer map | 49 * The cached lookup-table to associate doc comments with spans. The outer map |
| 54 * is from filenames to doc comments in that file. The inner map maps from the | 50 * is from filenames to doc comments in that file. The inner map maps from the |
| 55 * token positions to doc comments. Each position is the starting offset of the | 51 * token positions to doc comments. Each position is the starting offset of the |
| 56 * next non-comment token *following* the doc comment. For example, the position | 52 * next non-comment token *following* the doc comment. For example, the position |
| 57 * for this comment would be the position of the "Map" token below. | 53 * for this comment would be the position of the "Map" token below. |
| 58 */ | 54 */ |
| 59 Map<String, Map<int, String>> _comments; | 55 Map<String, Map<int, String>> _comments; |
| 60 | 56 |
| 61 int _totalLibraries = 0; | 57 int _totalLibraries = 0; |
| 62 int _totalTypes = 0; | 58 int _totalTypes = 0; |
| 63 int _totalMembers = 0; | 59 int _totalMembers = 0; |
| 64 | 60 |
| 65 FileSystem files; | |
| 66 | |
| 67 /** | 61 /** |
| 68 * Run this from the `utils/dartdoc` directory. | 62 * Run this from the `utils/dartdoc` directory. |
| 69 */ | 63 */ |
| 70 void main() { | 64 void main() { |
| 71 // The entrypoint of the library to generate docs for. | 65 // The entrypoint of the library to generate docs for. |
| 72 final libPath = process.argv[2]; | 66 final libPath = process.argv[2]; |
| 73 | 67 |
| 74 // Parse the dartdoc options. | 68 // Parse the dartdoc options. |
| 75 for (int i = 3; i < process.argv.length; i++) { | 69 for (int i = 3; i < process.argv.length; i++) { |
| 76 final arg = process.argv[i]; | 70 final arg = process.argv[i]; |
| (...skipping 18 matching lines...) Expand all Loading... |
| 95 md.setImplicitLinkResolver(resolveNameReference); | 89 md.setImplicitLinkResolver(resolveNameReference); |
| 96 | 90 |
| 97 final elapsed = time(() { | 91 final elapsed = time(() { |
| 98 initializeDartDoc(); | 92 initializeDartDoc(); |
| 99 | 93 |
| 100 initializeWorld(files); | 94 initializeWorld(files); |
| 101 | 95 |
| 102 world.processDartScript(libPath); | 96 world.processDartScript(libPath); |
| 103 world.resolveAll(); | 97 world.resolveAll(); |
| 104 | 98 |
| 105 // Clean the output directory. | |
| 106 if (files.fileExists(outdir)) { | |
| 107 files.removeDirectory(outdir, recursive: true); | |
| 108 } | |
| 109 files.createDirectory(outdir, recursive: true); | |
| 110 | |
| 111 // Copy over the static files. | |
| 112 for (final file in ['interact.js', 'styles.css']) { | |
| 113 copyStatic(file); | |
| 114 } | |
| 115 | |
| 116 // Generate the docs. | 99 // Generate the docs. |
| 117 for (final library in world.libraries.getValues()) { | 100 for (final library in world.libraries.getValues()) { |
| 118 docLibrary(library); | 101 docLibrary(library); |
| 119 } | 102 } |
| 120 | 103 |
| 121 docIndex(world.libraries.getValues()); | 104 docIndex(world.libraries.getValues()); |
| 122 }); | 105 }); |
| 123 | 106 |
| 124 print('Documented $_totalLibraries libraries, $_totalTypes types, and ' + | 107 print('Documented $_totalLibraries libraries, $_totalTypes types, and ' + |
| 125 '$_totalMembers members in ${elapsed}msec.'); | 108 '$_totalMembers members in ${elapsed}msec.'); |
| 126 } | 109 } |
| 127 | 110 |
| 128 void initializeDartDoc() { | 111 void initializeDartDoc() { |
| 129 _comments = <String, Map<int, String>>{}; | 112 _comments = <String, Map<int, String>>{}; |
| 130 } | 113 } |
| 131 | 114 |
| 132 /** Copies the static file at 'static/file' to the output directory. */ | |
| 133 copyStatic(String file) { | |
| 134 var contents = files.readAll(joinPaths('static', file)); | |
| 135 files.writeString(joinPaths(outdir, file), contents); | |
| 136 } | |
| 137 | |
| 138 num time(callback()) { | |
| 139 // Unlike world.withTiming, returns the elapsed time. | |
| 140 final watch = new Stopwatch(); | |
| 141 watch.start(); | |
| 142 callback(); | |
| 143 watch.stop(); | |
| 144 return watch.elapsedInMs(); | |
| 145 } | |
| 146 | |
| 147 startFile(String path) { | |
| 148 _filePath = path; | |
| 149 _file = new StringBuffer(); | |
| 150 } | |
| 151 | |
| 152 write(String s) { | |
| 153 _file.add(s); | |
| 154 } | |
| 155 | |
| 156 writeln(String s) { | |
| 157 write(s); | |
| 158 write('\n'); | |
| 159 } | |
| 160 | |
| 161 endFile() { | |
| 162 String outPath = '$outdir/$_filePath'; | |
| 163 files.createDirectory(dirname(outPath), recursive: true); | |
| 164 | |
| 165 world.files.writeString(outPath, _file.toString()); | |
| 166 _filePath = null; | |
| 167 _file = null; | |
| 168 } | |
| 169 | |
| 170 /** Turns a library name into something that's safe to use as a file name. */ | |
| 171 sanitize(String name) => name.replaceAll(':', '_').replaceAll('/', '_'); | |
| 172 | |
| 173 docIndex(List<Library> libraries) { | 115 docIndex(List<Library> libraries) { |
| 174 startFile('index.html'); | 116 startFile('index.html'); |
| 175 // TODO(rnystrom): Need to figure out what this should look like. | 117 // TODO(rnystrom): Need to figure out what this should look like. |
| 176 writeln( | 118 writeln( |
| 177 ''' | 119 ''' |
| 178 <html><head> | 120 <html><head> |
| 179 <title>Index</title> | 121 <title>Index</title> |
| 180 <link rel="stylesheet" type="text/css" href="styles.css" /> | 122 <link rel="stylesheet" type="text/css" href="styles.css" /> |
| 181 </head> | 123 </head> |
| 182 <body> | 124 <body> |
| (...skipping 14 matching lines...) Expand all Loading... |
| 197 writeln( | 139 writeln( |
| 198 ''' | 140 ''' |
| 199 </ul> | 141 </ul> |
| 200 </div> | 142 </div> |
| 201 </body></html> | 143 </body></html> |
| 202 '''); | 144 '''); |
| 203 | 145 |
| 204 endFile(); | 146 endFile(); |
| 205 } | 147 } |
| 206 | 148 |
| 207 /** Returns the number of times [search] occurs in [text]. */ | |
| 208 int countOccurrences(String text, String search) { | |
| 209 int start = 0; | |
| 210 int count = 0; | |
| 211 | |
| 212 while (true) { | |
| 213 start = text.indexOf(search, start); | |
| 214 if (start == -1) break; | |
| 215 count++; | |
| 216 // Offsetting by needle length means overlapping needles are not counted. | |
| 217 start += search.length; | |
| 218 } | |
| 219 | |
| 220 return count; | |
| 221 } | |
| 222 | |
| 223 /** Repeats [text] [count] times, separated by [separator] if given. */ | |
| 224 String repeat(String text, int count, [String separator]) { | |
| 225 // TODO(rnystrom): Should be in corelib. | |
| 226 final buffer = new StringBuffer(); | |
| 227 for (int i = 0; i < count; i++) { | |
| 228 buffer.add(text); | |
| 229 if ((i < count - 1) && (separator !== null)) buffer.add(separator); | |
| 230 } | |
| 231 | |
| 232 return buffer.toString(); | |
| 233 } | |
| 234 | |
| 235 /** | |
| 236 * Converts [absolute] which is understood to be a full path from the root of | |
| 237 * the generated docs to one relative to the current file. | |
| 238 */ | |
| 239 String relativePath(String absolute) { | |
| 240 // TODO(rnystrom): Walks all the way up to root each time. Shouldn't do this | |
| 241 // if the paths overlap. | |
| 242 return repeat('../', countOccurrences(_filePath, '/')) + absolute; | |
| 243 } | |
| 244 | |
| 245 /** | |
| 246 * Creates a hyperlink. Handles turning the [href] into an appropriate relative | |
| 247 * path from the current file. | |
| 248 */ | |
| 249 String a(String href, String contents, [String class]) { | |
| 250 final css = class == null ? '' : ' class="$class"'; | |
| 251 return '<a href="${relativePath(href)}"$css>$contents</a>'; | |
| 252 } | |
| 253 | |
| 254 writeHeader(String title) { | 149 writeHeader(String title) { |
| 255 writeln( | 150 writeln( |
| 256 ''' | 151 ''' |
| 257 <!DOCTYPE html> | 152 <!DOCTYPE html> |
| 258 <html> | 153 <html> |
| 259 <head> | 154 <head> |
| 260 <meta charset="utf-8"> | 155 <meta charset="utf-8"> |
| 261 <title>$title</title> | 156 <title>$title</title> |
| 262 <link rel="stylesheet" type="text/css" | 157 <link rel="stylesheet" type="text/css" |
| 263 href="${relativePath('styles.css')}" /> | 158 href="${relativePath('styles.css')}" /> |
| 264 <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700,8
00" rel="stylesheet" type="text/css"> | 159 <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700,8
00" rel="stylesheet" type="text/css"> |
| 265 <script src="${relativePath('interact.js')}"></script> | 160 <script src="${relativePath('interact.js')}"></script> |
| 266 </head> | 161 </head> |
| 267 <body> | 162 <body> |
| 268 <div class="content"> | 163 <div class="page"> |
| 269 '''); | 164 '''); |
| 165 docNavigation(); |
| 166 writeln('<div class="content">'); |
| 270 } | 167 } |
| 271 | 168 |
| 272 writeFooter() { | 169 writeFooter() { |
| 273 writeln( | 170 writeln( |
| 274 ''' | 171 ''' |
| 275 </div> | 172 </div> |
| 173 <div class="footer"</div> |
| 276 </body></html> | 174 </body></html> |
| 277 '''); | 175 '''); |
| 278 } | 176 } |
| 279 | 177 |
| 178 docNavigation() { |
| 179 writeln( |
| 180 ''' |
| 181 <div class="nav"> |
| 182 <h1>Libraries</h1> |
| 183 '''); |
| 184 |
| 185 for (final library in orderValuesByKeys(world.libraries)) { |
| 186 write('<h2><div class="icon-library"></div> '); |
| 187 |
| 188 if ((_currentLibrary == library) && (_currentType == null)) { |
| 189 write('<strong>${library.name}</strong>'); |
| 190 } else { |
| 191 write('${a(libraryUrl(library), library.name)}'); |
| 192 } |
| 193 write('</h2>'); |
| 194 |
| 195 final types = orderValuesByKeys(library.types); |
| 196 if (types.length > 0) { |
| 197 writeln('<ul>'); |
| 198 for (final type in types) { |
| 199 if (type.isTop) continue; |
| 200 if (type.name.startsWith('_')) continue; |
| 201 |
| 202 var icon = type.isClass ? 'icon-class' : 'icon-interface'; |
| 203 write('<li><div class="$icon"></div> '); |
| 204 |
| 205 if (_currentType == type) { |
| 206 write('<strong>${type.name}</strong>'); |
| 207 } else { |
| 208 write('${a(typeUrl(type), type.name)}'); |
| 209 } |
| 210 |
| 211 writeln('</li>'); |
| 212 } |
| 213 |
| 214 writeln('</ul>'); |
| 215 } |
| 216 } |
| 217 |
| 218 writeln('</div>'); |
| 219 } |
| 220 |
| 280 docLibrary(Library library) { | 221 docLibrary(Library library) { |
| 281 _totalLibraries++; | 222 _totalLibraries++; |
| 282 _currentLibrary = library; | 223 _currentLibrary = library; |
| 224 _currentType = null; |
| 283 | 225 |
| 284 startFile(libraryUrl(library)); | 226 startFile(libraryUrl(library)); |
| 285 writeHeader(library.name); | 227 writeHeader(library.name); |
| 286 writeln('<h1>Library <strong>${library.name}</strong></h1>'); | 228 writeln('<h1>Library <strong>${library.name}</strong></h1>'); |
| 287 | 229 |
| 288 // Look for a comment for the entire library. | 230 // Look for a comment for the entire library. |
| 289 final comment = findCommentInFile(library.baseSource, _libraryDoc); | 231 final comment = findCommentInFile(library.baseSource, _libraryDoc); |
| 290 if (comment != null) { | 232 if (comment != null) { |
| 291 final html = md.markdownToHtml(comment); | 233 final html = md.markdownToHtml(comment); |
| 292 writeln('<div class="doc">$html</div>'); | 234 writeln('<div class="doc">$html</div>'); |
| 293 } | 235 } |
| 294 | 236 |
| 295 // Document the top-level members. | 237 // Document the top-level members. |
| 296 docMembers(library.topType); | 238 docMembers(library.topType); |
| 297 | 239 |
| 298 // TODO(rnystrom): Link to types. | 240 // TODO(rnystrom): Link to types. |
| 299 writeln('<h3>Types</h3>'); | 241 writeln('<h3>Types</h3>'); |
| 300 | 242 |
| 301 for (final type in orderValuesByKeys(library.types)) { | 243 for (final type in orderValuesByKeys(library.types)) { |
| 302 if (type.isTop) continue; | 244 if (type.isTop) continue; |
| 245 if (type.name.startsWith('_')) continue; |
| 303 writeln( | 246 writeln( |
| 304 ''' | 247 ''' |
| 305 <div class="type"> | 248 <div class="type"> |
| 306 <h4> | 249 <h4> |
| 307 ${type.isClass ? "class" : "interface"} | 250 ${type.isClass ? "class" : "interface"} |
| 308 ${a(typeUrl(type), "<strong>${type.name}</strong>")} | 251 ${a(typeUrl(type), "<strong>${type.name}</strong>")} |
| 309 </h4> | 252 </h4> |
| 310 </div> | 253 </div> |
| 311 '''); | 254 '''); |
| 312 } | 255 } |
| (...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 449 if (method.isConstructor) { | 392 if (method.isConstructor) { |
| 450 write(method.isConst ? 'const ' : 'new '); | 393 write(method.isConst ? 'const ' : 'new '); |
| 451 } | 394 } |
| 452 | 395 |
| 453 if (constructorName == null) { | 396 if (constructorName == null) { |
| 454 write(annotation(type, method.returnType)); | 397 write(annotation(type, method.returnType)); |
| 455 } | 398 } |
| 456 | 399 |
| 457 // Translate specially-named methods: getters, setters, operators. | 400 // Translate specially-named methods: getters, setters, operators. |
| 458 var name = method.name; | 401 var name = method.name; |
| 459 if (name.startsWith('get\$')) { | 402 if (name.startsWith('get:')) { |
| 460 // Getter. | 403 // Getter. |
| 461 name = 'get ${name.substring(4)}'; | 404 name = 'get ${name.substring(4)}'; |
| 462 } else if (name.startsWith('set\$')) { | 405 } else if (name.startsWith('set:')) { |
| 463 // Setter. | 406 // Setter. |
| 464 name = 'set ${name.substring(4)}'; | 407 name = 'set ${name.substring(4)}'; |
| 465 } else { | 408 } else { |
| 466 // See if it's an operator. | 409 // See if it's an operator. |
| 467 name = TokenKind.rawOperatorFromMethod(name); | 410 name = TokenKind.rawOperatorFromMethod(name); |
| 468 if (name == null) { | 411 if (name == null) { |
| 469 name = method.name; | 412 name = method.name; |
| 470 } else { | 413 } else { |
| 471 name = 'operator $name'; | 414 name = 'operator $name'; |
| 472 } | 415 } |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 522 <strong>${field.name}</strong> <a class="anchor-link" | 465 <strong>${field.name}</strong> <a class="anchor-link" |
| 523 href="#${memberAnchor(field)}" | 466 href="#${memberAnchor(field)}" |
| 524 title="Permalink to ${type.name}.${field.name}">#</a> | 467 title="Permalink to ${type.name}.${field.name}">#</a> |
| 525 </h4> | 468 </h4> |
| 526 '''); | 469 '''); |
| 527 | 470 |
| 528 docCode(field.span, showCode: true); | 471 docCode(field.span, showCode: true); |
| 529 writeln('</div>'); | 472 writeln('</div>'); |
| 530 } | 473 } |
| 531 | 474 |
| 475 /** |
| 476 * Creates a hyperlink. Handles turning the [href] into an appropriate relative |
| 477 * path from the current file. |
| 478 */ |
| 479 String a(String href, String contents, [String class]) { |
| 480 final css = class == null ? '' : ' class="$class"'; |
| 481 return '<a href="${relativePath(href)}"$css>$contents</a>'; |
| 482 } |
| 483 |
| 532 /** Generates a human-friendly string representation for a type. */ | 484 /** Generates a human-friendly string representation for a type. */ |
| 533 typeName(Type type) { | 485 typeName(Type type) { |
| 534 // See if it's a generic type. | 486 // See if it's a generic type. |
| 535 if (type.isGeneric) { | 487 if (type.isGeneric) { |
| 536 final typeParams = type.genericType.typeParameters; | 488 final typeParams = type.genericType.typeParameters; |
| 537 final params = Strings.join(map(typeParams, (p) => p.name), ', '); | 489 final params = Strings.join(map(typeParams, (p) => p.name), ', '); |
| 538 return '${type.name}<$params>'; | 490 return '${type.name}<$params>'; |
| 539 } | 491 } |
| 540 | 492 |
| 541 // See if it's an instantiation of a generic type. | 493 // See if it's an instantiation of a generic type. |
| 542 final typeArgs = type.typeArgsInOrder; | 494 final typeArgs = type.typeArgsInOrder; |
| 543 if (typeArgs != null) { | 495 if (typeArgs != null) { |
| 544 final args = Strings.join(map(typeArgs, typeName), ', '); | 496 final args = Strings.join(map(typeArgs, typeName), ', '); |
| 545 return '${type.genericType.name}<$args>'; | 497 return '${type.genericType.name}<$args>'; |
| 546 } | 498 } |
| 547 | 499 |
| 548 // Regular type. | 500 // Regular type. |
| 549 return type.name; | 501 return type.name; |
| 550 } | 502 } |
| 551 | 503 |
| 552 /** Gets the URL to the documentation for [library]. */ | |
| 553 libraryUrl(Library library) { | |
| 554 return '${sanitize(library.name)}.html'; | |
| 555 } | |
| 556 | |
| 557 /** Gets the URL for the documentation for [type]. */ | |
| 558 typeUrl(Type type) { | |
| 559 // Always get the generic type to strip off any type parameters or arguments. | |
| 560 // If the type isn't generic, genericType returns `this`, so it works for | |
| 561 // non-generic types too. | |
| 562 return '${sanitize(type.library.name)}/${type.genericType.name}.html'; | |
| 563 } | |
| 564 | |
| 565 /** Gets the URL for the documentation for [member]. */ | |
| 566 memberUrl(Member member) { | |
| 567 return '${typeUrl(member.declaringType)}#${member.name}'; | |
| 568 } | |
| 569 | |
| 570 /** Gets the anchor id for the document for [member]. */ | |
| 571 memberAnchor(Member member) => '${member.name}'; | |
| 572 | |
| 573 /** Writes a linked cross reference to [type]. */ | 504 /** Writes a linked cross reference to [type]. */ |
| 574 typeReference(Type type) { | 505 typeReference(Type type) { |
| 575 // TODO(rnystrom): Do we need to handle ParameterTypes here like | 506 // TODO(rnystrom): Do we need to handle ParameterTypes here like |
| 576 // annotation() does? | 507 // annotation() does? |
| 577 return a(typeUrl(type), typeName(type), class: 'crossref'); | 508 return a(typeUrl(type), typeName(type), class: 'crossref'); |
| 578 } | 509 } |
| 579 | 510 |
| 580 /** | 511 /** |
| 581 * Creates a linked string for an optional type annotation. Returns an empty | 512 * Creates a linked string for an optional type annotation. Returns an empty |
| 582 * string if the type is Dynamic. | 513 * string if the type is Dynamic. |
| (...skipping 177 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 760 // Syntax highlight. | 691 // Syntax highlight. |
| 761 return classifySource(new SourceFile('', code)); | 692 return classifySource(new SourceFile('', code)); |
| 762 } | 693 } |
| 763 | 694 |
| 764 // TODO(rnystrom): Move into SourceSpan? | 695 // TODO(rnystrom): Move into SourceSpan? |
| 765 int getSpanColumn(SourceSpan span) { | 696 int getSpanColumn(SourceSpan span) { |
| 766 final line = span.file.getLine(span.start); | 697 final line = span.file.getLine(span.start); |
| 767 return span.file.getColumn(line, span.start); | 698 return span.file.getColumn(line, span.start); |
| 768 } | 699 } |
| 769 | 700 |
| 770 /** Removes up to [indentation] leading whitespace characters from [text]. */ | |
| 771 unindent(String text, int indentation) { | |
| 772 var start; | |
| 773 for (start = 0; start < Math.min(indentation, text.length); start++) { | |
| 774 // Stop if we hit a non-whitespace character. | |
| 775 if (text[start] != ' ') break; | |
| 776 } | |
| 777 | |
| 778 return text.substring(start); | |
| 779 } | |
| 780 | |
| 781 /** | 701 /** |
| 782 * Pulls the raw text out of a doc comment (i.e. removes the comment | 702 * Pulls the raw text out of a doc comment (i.e. removes the comment |
| 783 * characters). | 703 * characters). |
| 784 */ | 704 */ |
| 785 stripComment(comment) { | 705 stripComment(comment) { |
| 786 StringBuffer buf = new StringBuffer(); | 706 StringBuffer buf = new StringBuffer(); |
| 787 | 707 |
| 788 for (final line in comment.split('\n')) { | 708 for (final line in comment.split('\n')) { |
| 789 line = line.trim(); | 709 line = line.trim(); |
| 790 if (line.startsWith('/**')) line = line.substring(3, line.length); | 710 if (line.startsWith('/**')) line = line.substring(3, line.length); |
| 791 if (line.endsWith('*/')) line = line.substring(0, line.length - 2); | 711 if (line.endsWith('*/')) line = line.substring(0, line.length - 2); |
| 792 line = line.trim(); | 712 line = line.trim(); |
| 793 if (line.startsWith('* ')) { | 713 if (line.startsWith('* ')) { |
| 794 line = line.substring(2, line.length); | 714 line = line.substring(2, line.length); |
| 795 } else if (line.startsWith('*')) { | 715 } else if (line.startsWith('*')) { |
| 796 line = line.substring(1, line.length); | 716 line = line.substring(1, line.length); |
| 797 } | 717 } |
| 798 | 718 |
| 799 buf.add(line); | 719 buf.add(line); |
| 800 buf.add('\n'); | 720 buf.add('\n'); |
| 801 } | 721 } |
| 802 | 722 |
| 803 return buf.toString(); | 723 return buf.toString(); |
| 804 } | 724 } |
| OLD | NEW |