| 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>${typeName(type)}</strong>")} | 251 ${a(typeUrl(type), "<strong>${typeName(type)}</strong>")} |
| 309 </h4> | 252 </h4> |
| 310 </div> | 253 </div> |
| 311 '''); | 254 '''); |
| 312 } | 255 } |
| (...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 462 if (method.isConstructor) { | 405 if (method.isConstructor) { |
| 463 write(method.isConst ? 'const ' : 'new '); | 406 write(method.isConst ? 'const ' : 'new '); |
| 464 } | 407 } |
| 465 | 408 |
| 466 if (constructorName == null) { | 409 if (constructorName == null) { |
| 467 write(annotation(type, method.returnType)); | 410 write(annotation(type, method.returnType)); |
| 468 } | 411 } |
| 469 | 412 |
| 470 // Translate specially-named methods: getters, setters, operators. | 413 // Translate specially-named methods: getters, setters, operators. |
| 471 var name = method.name; | 414 var name = method.name; |
| 472 if (name.startsWith('get\$')) { | 415 if (name.startsWith('get:')) { |
| 473 // Getter. | 416 // Getter. |
| 474 name = 'get ${name.substring(4)}'; | 417 name = 'get ${name.substring(4)}'; |
| 475 } else if (name.startsWith('set\$')) { | 418 } else if (name.startsWith('set:')) { |
| 476 // Setter. | 419 // Setter. |
| 477 name = 'set ${name.substring(4)}'; | 420 name = 'set ${name.substring(4)}'; |
| 478 } else { | 421 } else { |
| 479 // See if it's an operator. | 422 // See if it's an operator. |
| 480 name = TokenKind.rawOperatorFromMethod(name); | 423 name = TokenKind.rawOperatorFromMethod(name); |
| 481 if (name == null) { | 424 if (name == null) { |
| 482 name = method.name; | 425 name = method.name; |
| 483 } else { | 426 } else { |
| 484 name = 'operator $name'; | 427 name = 'operator $name'; |
| 485 } | 428 } |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 535 <strong>${field.name}</strong> <a class="anchor-link" | 478 <strong>${field.name}</strong> <a class="anchor-link" |
| 536 href="#${memberAnchor(field)}" | 479 href="#${memberAnchor(field)}" |
| 537 title="Permalink to ${typeName(type)}.${field.name}">#</a> | 480 title="Permalink to ${typeName(type)}.${field.name}">#</a> |
| 538 </h4> | 481 </h4> |
| 539 '''); | 482 '''); |
| 540 | 483 |
| 541 docCode(field.span, showCode: true); | 484 docCode(field.span, showCode: true); |
| 542 writeln('</div>'); | 485 writeln('</div>'); |
| 543 } | 486 } |
| 544 | 487 |
| 488 /** |
| 489 * Creates a hyperlink. Handles turning the [href] into an appropriate relative |
| 490 * path from the current file. |
| 491 */ |
| 492 String a(String href, String contents, [String class]) { |
| 493 final css = class == null ? '' : ' class="$class"'; |
| 494 return '<a href="${relativePath(href)}"$css>$contents</a>'; |
| 495 } |
| 496 |
| 545 /** Generates a human-friendly string representation for a type. */ | 497 /** Generates a human-friendly string representation for a type. */ |
| 546 typeName(Type type) { | 498 typeName(Type type) { |
| 547 // See if it's a generic type. | 499 // See if it's a generic type. |
| 548 if (type.isGeneric) { | 500 if (type.isGeneric) { |
| 549 final typeParams = type.genericType.typeParameters; | 501 final typeParams = type.genericType.typeParameters; |
| 550 final params = Strings.join(map(typeParams, (p) => p.name), ', '); | 502 final params = Strings.join(map(typeParams, (p) => p.name), ', '); |
| 551 return '${type.name}<$params>'; | 503 return '${type.name}<$params>'; |
| 552 } | 504 } |
| 553 | 505 |
| 554 // See if it's an instantiation of a generic type. | 506 // See if it's an instantiation of a generic type. |
| 555 final typeArgs = type.typeArgsInOrder; | 507 final typeArgs = type.typeArgsInOrder; |
| 556 if (typeArgs != null) { | 508 if (typeArgs != null) { |
| 557 final args = Strings.join(map(typeArgs, typeName), ', '); | 509 final args = Strings.join(map(typeArgs, typeName), ', '); |
| 558 return '${type.genericType.name}<$args>'; | 510 return '${type.genericType.name}<$args>'; |
| 559 } | 511 } |
| 560 | 512 |
| 561 // Regular type. | 513 // Regular type. |
| 562 return type.name; | 514 return type.name; |
| 563 } | 515 } |
| 564 | 516 |
| 565 /** Gets the URL to the documentation for [library]. */ | |
| 566 libraryUrl(Library library) { | |
| 567 return '${sanitize(library.name)}.html'; | |
| 568 } | |
| 569 | |
| 570 /** Gets the URL for the documentation for [type]. */ | |
| 571 typeUrl(Type type) { | |
| 572 // Always get the generic type to strip off any type parameters or arguments. | |
| 573 // If the type isn't generic, genericType returns `this`, so it works for | |
| 574 // non-generic types too. | |
| 575 return '${sanitize(type.library.name)}/${type.genericType.name}.html'; | |
| 576 } | |
| 577 | |
| 578 /** Gets the URL for the documentation for [member]. */ | |
| 579 memberUrl(Member member) { | |
| 580 return '${typeUrl(member.declaringType)}#${member.name}'; | |
| 581 } | |
| 582 | |
| 583 /** Gets the anchor id for the document for [member]. */ | |
| 584 memberAnchor(Member member) => '${member.name}'; | |
| 585 | |
| 586 /** Writes a linked cross reference to [type]. */ | 517 /** Writes a linked cross reference to [type]. */ |
| 587 typeReference(Type type) { | 518 typeReference(Type type) { |
| 588 // TODO(rnystrom): Do we need to handle ParameterTypes here like | 519 // TODO(rnystrom): Do we need to handle ParameterTypes here like |
| 589 // annotation() does? | 520 // annotation() does? |
| 590 return a(typeUrl(type), typeName(type), class: 'crossref'); | 521 return a(typeUrl(type), typeName(type), class: 'crossref'); |
| 591 } | 522 } |
| 592 | 523 |
| 593 /** | 524 /** |
| 594 * Creates a linked string for an optional type annotation. Returns an empty | 525 * Creates a linked string for an optional type annotation. Returns an empty |
| 595 * string if the type is Dynamic. | 526 * string if the type is Dynamic. |
| (...skipping 177 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 773 // Syntax highlight. | 704 // Syntax highlight. |
| 774 return classifySource(new SourceFile('', code)); | 705 return classifySource(new SourceFile('', code)); |
| 775 } | 706 } |
| 776 | 707 |
| 777 // TODO(rnystrom): Move into SourceSpan? | 708 // TODO(rnystrom): Move into SourceSpan? |
| 778 int getSpanColumn(SourceSpan span) { | 709 int getSpanColumn(SourceSpan span) { |
| 779 final line = span.file.getLine(span.start); | 710 final line = span.file.getLine(span.start); |
| 780 return span.file.getColumn(line, span.start); | 711 return span.file.getColumn(line, span.start); |
| 781 } | 712 } |
| 782 | 713 |
| 783 /** Removes up to [indentation] leading whitespace characters from [text]. */ | |
| 784 unindent(String text, int indentation) { | |
| 785 var start; | |
| 786 for (start = 0; start < Math.min(indentation, text.length); start++) { | |
| 787 // Stop if we hit a non-whitespace character. | |
| 788 if (text[start] != ' ') break; | |
| 789 } | |
| 790 | |
| 791 return text.substring(start); | |
| 792 } | |
| 793 | |
| 794 /** | 714 /** |
| 795 * Pulls the raw text out of a doc comment (i.e. removes the comment | 715 * Pulls the raw text out of a doc comment (i.e. removes the comment |
| 796 * characters). | 716 * characters). |
| 797 */ | 717 */ |
| 798 stripComment(comment) { | 718 stripComment(comment) { |
| 799 StringBuffer buf = new StringBuffer(); | 719 StringBuffer buf = new StringBuffer(); |
| 800 | 720 |
| 801 for (final line in comment.split('\n')) { | 721 for (final line in comment.split('\n')) { |
| 802 line = line.trim(); | 722 line = line.trim(); |
| 803 if (line.startsWith('/**')) line = line.substring(3, line.length); | 723 if (line.startsWith('/**')) line = line.substring(3, line.length); |
| 804 if (line.endsWith('*/')) line = line.substring(0, line.length - 2); | 724 if (line.endsWith('*/')) line = line.substring(0, line.length - 2); |
| 805 line = line.trim(); | 725 line = line.trim(); |
| 806 if (line.startsWith('* ')) { | 726 if (line.startsWith('* ')) { |
| 807 line = line.substring(2, line.length); | 727 line = line.substring(2, line.length); |
| 808 } else if (line.startsWith('*')) { | 728 } else if (line.startsWith('*')) { |
| 809 line = line.substring(1, line.length); | 729 line = line.substring(1, line.length); |
| 810 } | 730 } |
| 811 | 731 |
| 812 buf.add(line); | 732 buf.add(line); |
| 813 buf.add('\n'); | 733 buf.add('\n'); |
| 814 } | 734 } |
| 815 | 735 |
| 816 return buf.toString(); | 736 return buf.toString(); |
| 817 } | 737 } |
| OLD | NEW |