| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 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. | |
| 4 | |
| 5 /** | |
| 6 * The docgen tool takes in a library as input and produces documentation | |
| 7 * for the library as well as all libraries it imports and uses. The tool can | |
| 8 * be run by passing in the path to a .dart file like this: | |
| 9 * | |
| 10 * dart docgen.dart [OPTIONS] [FILE/DIR] | |
| 11 * | |
| 12 * This outputs information about all classes, variables, functions, and | |
| 13 * methods defined in the library and its imported libraries. | |
| 14 */ | |
| 15 library docgen; | |
| 16 | |
| 17 import 'dart:io'; | |
| 18 import 'dart:json'; | |
| 19 import 'dart:async'; | |
| 20 import 'package:markdown/markdown.dart' as markdown; | |
| 21 import 'package:args/args.dart'; | |
| 22 import 'dart2yaml.dart'; | |
| 23 import 'package:compiler_unsupported/compiler.dart' as api; | |
| 24 import 'package:compiler_unsupported/implementation/filenames.dart'; | |
| 25 import 'package:compiler_unsupported/implementation/mirrors/dart2js_mirror.dart' | |
| 26 as dart2js; | |
| 27 import 'package:compiler_unsupported/implementation/mirrors/mirrors.dart'; | |
| 28 import 'package:compiler_unsupported/implementation/mirrors/mirrors_util.dart'; | |
| 29 import 'package:compiler_unsupported/implementation/source_file_provider.dart'; | |
| 30 import 'package:logging/logging.dart'; | |
| 31 | |
| 32 /// Logger for Dart Doc Generator. | |
| 33 var logger = new Logger("Docgen"); | |
| 34 | |
| 35 /// Unique ID, will get incremented everytime an ID is requested. | |
| 36 int _uid = 0; | |
| 37 | |
| 38 int getID() => _uid++; | |
| 39 | |
| 40 const String usage = "Usage: dart docgen.dart [OPTIONS] [FILE/DIR]"; | |
| 41 | |
| 42 /** | |
| 43 * Returns a ArgParser with all the flags and options created. | |
| 44 */ | |
| 45 ArgParser initArgParser() { | |
| 46 var parser = new ArgParser(); | |
| 47 parser.addFlag("help", abbr: "h", | |
| 48 help: "Prints help and usage information", | |
| 49 negatable: false, | |
| 50 callback: (help) { | |
| 51 if (help) print(parser.getUsage()); | |
| 52 }); | |
| 53 parser.addFlag("verbose", abbr: "v", | |
| 54 help: "Runs docgen with logging. ", | |
| 55 defaultsTo: false, negatable: false, | |
| 56 callback: (verbose) { | |
| 57 if (verbose) logger.onRecord.listen((record) => print(record.message)); | |
| 58 }); | |
| 59 parser.addFlag("yaml", abbr: "y", | |
| 60 help: "Outputs to YAML", | |
| 61 defaultsTo: true, negatable: true); | |
| 62 parser.addFlag("json", abbr: "j", | |
| 63 help: "Outputs to JSON", | |
| 64 defaultsTo: false, negatable: true); | |
| 65 parser.addFlag("hide-private", | |
| 66 help: "Hides private declarations" , | |
| 67 defaultsTo: false, negatable: false); | |
| 68 parser.addFlag("sdk", | |
| 69 help: "Flag to parse SDK Library files", | |
| 70 defaultsTo: true, negatable: true); | |
| 71 | |
| 72 return parser; | |
| 73 } | |
| 74 | |
| 75 List<Path> listLibraries(List<String> args) { | |
| 76 if (args.length != 1) { | |
| 77 throw new UnsupportedError(usage); | |
| 78 } | |
| 79 var libraries = new List<Path>(); | |
| 80 var type = FileSystemEntity.typeSync(args[0]); | |
| 81 | |
| 82 if (type == FileSystemEntityType.NOT_FOUND) { | |
| 83 throw new UnsupportedError("File does not exist. $usage"); | |
| 84 } else if (type == FileSystemEntityType.LINK) { | |
| 85 libraries.addAll(listLibrariesFromDir(new Link(args[0]).targetSync())); | |
| 86 } else if (type == FileSystemEntityType.FILE) { | |
| 87 libraries.add(new Path(args[0])); | |
| 88 logger.info("Added to libraries: ${libraries.last.toString()}"); | |
| 89 } else if (type == FileSystemEntityType.DIRECTORY) { | |
| 90 libraries.addAll(listLibrariesFromDir(args[0])); | |
| 91 } | |
| 92 return libraries; | |
| 93 } | |
| 94 | |
| 95 List<Path> listLibrariesFromDir(String path) { | |
| 96 var libraries = new List<Path>(); | |
| 97 new Directory(path).listSync(recursive: true, | |
| 98 followLinks: true).forEach((file) { | |
| 99 if (new Path(file.path).extension == "dart") { | |
| 100 if (!file.path.contains("/packages/")) { | |
| 101 libraries.add(new Path(file.path)); | |
| 102 logger.info("Added to libraries: ${libraries.last.toString()}"); | |
| 103 } | |
| 104 } | |
| 105 }); | |
| 106 return libraries; | |
| 107 } | |
| 108 | |
| 109 /** | |
| 110 * This class documents a list of libraries. | |
| 111 */ | |
| 112 class Docgen { | |
| 113 | |
| 114 /// Libraries to be documented. | |
| 115 List<LibraryMirror> _libraries; | |
| 116 | |
| 117 /// Saves list of libraries for Docgen object. | |
| 118 void set libraries(value) { | |
| 119 _libraries = value; | |
| 120 } | |
| 121 | |
| 122 /// Current library being documented to be used for comment links. | |
| 123 LibraryMirror _currentLibrary; | |
| 124 | |
| 125 /// Current class being documented to be used for comment links. | |
| 126 ClassMirror _currentClass; | |
| 127 | |
| 128 /// Current member being documented to be used for comment links. | |
| 129 MemberMirror _currentMember; | |
| 130 | |
| 131 /// Resolves reference links | |
| 132 markdown.Resolver linkResolver; | |
| 133 | |
| 134 /// Should the output file type be YAML? | |
| 135 bool outputToYaml; | |
| 136 /// Should the output file type be JSON? | |
| 137 bool outputToJson; | |
| 138 /// Should the output file hide private declarations? | |
| 139 bool hidePrivate; | |
| 140 /// Should the output include SDK libraries? | |
| 141 bool sdk; | |
| 142 | |
| 143 /** | |
| 144 * Docgen constructor initializes the link resolver for markdown parsing. | |
| 145 * Also initializes the command line arguments. | |
| 146 */ | |
| 147 Docgen({ArgResults argResults}) { | |
| 148 outputToYaml = argResults["yaml"]; | |
| 149 outputToJson = argResults["json"]; | |
| 150 hidePrivate = argResults["hide-private"]; | |
| 151 sdk = argResults["sdk"]; | |
| 152 | |
| 153 this.linkResolver = (name) => | |
| 154 fixReference(name, _currentLibrary, _currentClass, _currentMember); | |
| 155 } | |
| 156 | |
| 157 /** | |
| 158 * Analyzes set of libraries by getting a mirror system and triggers the | |
| 159 * documentation of the libraries. | |
| 160 */ | |
| 161 void analyze(List<Path> libraries) { | |
| 162 /// Assuming the dart executable is from the Dart SDK folder. | |
| 163 var sdkRoot = new Path(new Options().executable).directoryPath | |
| 164 .directoryPath; | |
| 165 logger.info("SDK Root: ${sdkRoot.toString()}"); | |
| 166 Path packageDir = libraries.last.directoryPath.append("packages"); | |
| 167 logger.info("Package Root: ${packageDir.toString()}"); | |
| 168 getMirrorSystem(libraries, sdkRoot, | |
| 169 packageRoot: packageDir).then((MirrorSystem mirrorSystem) { | |
| 170 if (mirrorSystem.libraries.values.isEmpty) { | |
| 171 throw new UnsupportedError("No Library Mirrors."); | |
| 172 } | |
| 173 this.libraries = mirrorSystem.libraries.values; | |
| 174 documentLibraries(); | |
| 175 }); | |
| 176 } | |
| 177 | |
| 178 /** | |
| 179 * Analyzes set of libraries and provides a mirror system which can be used | |
| 180 * for static inspection of the source code. | |
| 181 */ | |
| 182 Future<MirrorSystem> getMirrorSystem(List<Path> libraries, | |
| 183 Path libraryRoot, {Path packageRoot}) { | |
| 184 SourceFileProvider provider = new SourceFileProvider(); | |
| 185 api.DiagnosticHandler diagnosticHandler = | |
| 186 new FormattingDiagnosticHandler(provider).diagnosticHandler; | |
| 187 Uri libraryUri = currentDirectory.resolve(appendSlash('$libraryRoot')); | |
| 188 Uri packageUri = null; | |
| 189 if (packageRoot != null) { | |
| 190 packageUri = currentDirectory.resolve(appendSlash('$packageRoot')); | |
| 191 } | |
| 192 List<Uri> librariesUri = <Uri>[]; | |
| 193 libraries.forEach((library) { | |
| 194 librariesUri.add(currentDirectory.resolve(library.toString())); | |
| 195 }); | |
| 196 return dart2js.analyze(librariesUri, libraryUri, packageUri, | |
| 197 provider.readStringFromUri, diagnosticHandler, | |
| 198 ['--preserve-comments', '--categories=Client,Server']); | |
| 199 } | |
| 200 | |
| 201 /** | |
| 202 * Creates documentation for filtered libraries. | |
| 203 */ | |
| 204 void documentLibraries() { | |
| 205 _libraries.forEach((library) { | |
| 206 // Files belonging to the SDK have a uri that begins with "dart:". | |
| 207 if (sdk || !library.uri.toString().startsWith("dart:")) { | |
| 208 _currentLibrary = library; | |
| 209 var result = new Library(library.qualifiedName, _getComment(library), | |
| 210 _getVariables(library.variables), _getMethods(library.functions), | |
| 211 _getClasses(library.classes), getID()); | |
| 212 if (outputToJson) { | |
| 213 _writeToFile(stringify(result.toMap()), "${result.name}.json"); | |
| 214 } | |
| 215 if (outputToYaml) { | |
| 216 _writeToFile(getYamlString(result.toMap()), "${result.name}.yaml"); | |
| 217 } | |
| 218 } | |
| 219 }); | |
| 220 } | |
| 221 | |
| 222 /** | |
| 223 * Returns any documentation comments associated with a mirror with | |
| 224 * simple markdown converted to html. | |
| 225 */ | |
| 226 String _getComment(DeclarationMirror mirror) { | |
| 227 String commentText; | |
| 228 mirror.metadata.forEach((metadata) { | |
| 229 if (metadata is CommentInstanceMirror) { | |
| 230 CommentInstanceMirror comment = metadata; | |
| 231 if (comment.isDocComment) { | |
| 232 if (commentText == null) { | |
| 233 commentText = comment.trimmedText; | |
| 234 } else { | |
| 235 commentText = "$commentText ${comment.trimmedText}"; | |
| 236 } | |
| 237 } | |
| 238 } | |
| 239 }); | |
| 240 commentText = commentText == null ? "" : | |
| 241 markdown.markdownToHtml(commentText.trim(), linkResolver: linkResolver) | |
| 242 .replaceAll("\n", ""); | |
| 243 return commentText; | |
| 244 } | |
| 245 | |
| 246 /** | |
| 247 * Converts all [_] references in comments to <code>_</code>. | |
| 248 */ | |
| 249 // TODO(tmandel): Create proper links for [_] style markdown based | |
| 250 // on scope once layout of viewer is finished. | |
| 251 markdown.Node fixReference(String name, LibraryMirror currentLibrary, | |
| 252 ClassMirror currentClass, MemberMirror currentMember) { | |
| 253 return new markdown.Element.text('code', name); | |
| 254 } | |
| 255 | |
| 256 /** | |
| 257 * Returns a map of [Variable] objects constructed from inputted mirrors. | |
| 258 */ | |
| 259 Map<String, Variable> _getVariables(Map<String, VariableMirror> mirrorMap) { | |
| 260 var data = {}; | |
| 261 mirrorMap.forEach((String mirrorName, VariableMirror mirror) { | |
| 262 if (!hidePrivate || !mirror.isPrivate) { | |
| 263 _currentMember = mirror; | |
| 264 data[mirrorName] = new Variable(mirrorName, mirror.isFinal, | |
| 265 mirror.isStatic, mirror.type.toString(), _getComment(mirror), | |
| 266 getID()); | |
| 267 } | |
| 268 }); | |
| 269 return data; | |
| 270 } | |
| 271 | |
| 272 /** | |
| 273 * Returns a map of [Method] objects constructed from inputted mirrors. | |
| 274 */ | |
| 275 Map<String, Method> _getMethods(Map<String, MethodMirror> mirrorMap) { | |
| 276 var data = {}; | |
| 277 mirrorMap.forEach((String mirrorName, MethodMirror mirror) { | |
| 278 if (!hidePrivate || !mirror.isPrivate) { | |
| 279 _currentMember = mirror; | |
| 280 data[mirrorName] = new Method(mirrorName, mirror.isSetter, | |
| 281 mirror.isGetter, mirror.isConstructor, mirror.isOperator, | |
| 282 mirror.isStatic, mirror.returnType.toString(), _getComment(mirror), | |
| 283 _getParameters(mirror.parameters), getID()); | |
| 284 } | |
| 285 }); | |
| 286 return data; | |
| 287 } | |
| 288 | |
| 289 /** | |
| 290 * Returns a map of [Class] objects constructed from inputted mirrors. | |
| 291 */ | |
| 292 Map<String, Class> _getClasses(Map<String, ClassMirror> mirrorMap) { | |
| 293 var data = {}; | |
| 294 mirrorMap.forEach((String mirrorName, ClassMirror mirror) { | |
| 295 if (!hidePrivate || !mirror.isPrivate) { | |
| 296 _currentClass = mirror; | |
| 297 var superclass = (mirror.superclass != null) ? | |
| 298 mirror.superclass.qualifiedName : ""; | |
| 299 var interfaces = | |
| 300 mirror.superinterfaces.map((interface) => interface.qualifiedName); | |
| 301 data[mirrorName] = new Class(mirrorName, superclass, mirror.isAbstract, | |
| 302 mirror.isTypedef, _getComment(mirror), interfaces.toList(), | |
| 303 _getVariables(mirror.variables), _getMethods(mirror.methods), | |
| 304 getID()); | |
| 305 } | |
| 306 }); | |
| 307 return data; | |
| 308 } | |
| 309 | |
| 310 /** | |
| 311 * Returns a map of [Parameter] objects constructed from inputted mirrors. | |
| 312 */ | |
| 313 Map<String, Parameter> _getParameters(List<ParameterMirror> mirrorList) { | |
| 314 var data = {}; | |
| 315 mirrorList.forEach((ParameterMirror mirror) { | |
| 316 _currentMember = mirror; | |
| 317 data[mirror.simpleName] = new Parameter(mirror.simpleName, | |
| 318 mirror.isOptional, mirror.isNamed, mirror.hasDefaultValue, | |
| 319 mirror.type.toString(), mirror.defaultValue, getID()); | |
| 320 }); | |
| 321 return data; | |
| 322 } | |
| 323 } | |
| 324 | |
| 325 /** | |
| 326 * Transforms the map by calling toMap on each value in it. | |
| 327 */ | |
| 328 Map recurseMap(Map inputMap) { | |
| 329 var outputMap = {}; | |
| 330 inputMap.forEach((key, value) { | |
| 331 outputMap[key] = value.toMap(); | |
| 332 }); | |
| 333 return outputMap; | |
| 334 } | |
| 335 | |
| 336 /** | |
| 337 * A class containing contents of a Dart library. | |
| 338 */ | |
| 339 class Library { | |
| 340 | |
| 341 /// Unique ID number for resolving links. | |
| 342 int id; | |
| 343 | |
| 344 /// Documentation comment with converted markdown. | |
| 345 String comment; | |
| 346 | |
| 347 /// Top-level variables in the library. | |
| 348 Map<String, Variable> variables; | |
| 349 | |
| 350 /// Top-level functions in the library. | |
| 351 Map<String, Method> functions; | |
| 352 | |
| 353 /// Classes defined within the library | |
| 354 Map<String, Class> classes; | |
| 355 | |
| 356 String name; | |
| 357 | |
| 358 Library(this.name, this.comment, this.variables, | |
| 359 this.functions, this.classes, this.id); | |
| 360 | |
| 361 /// Generates a map describing the [Library] object. | |
| 362 Map toMap() { | |
| 363 var libraryMap = {}; | |
| 364 libraryMap["id"] = id; | |
| 365 libraryMap["name"] = name; | |
| 366 libraryMap["comment"] = comment; | |
| 367 libraryMap["variables"] = recurseMap(variables); | |
| 368 libraryMap["functions"] = recurseMap(functions); | |
| 369 libraryMap["classes"] = recurseMap(classes); | |
| 370 return libraryMap; | |
| 371 } | |
| 372 } | |
| 373 | |
| 374 /** | |
| 375 * A class containing contents of a Dart class. | |
| 376 */ | |
| 377 // TODO(tmandel): Figure out how to do typedefs (what is needed) | |
| 378 class Class { | |
| 379 | |
| 380 /// Unique ID number for resolving links. | |
| 381 int id; | |
| 382 | |
| 383 /// Documentation comment with converted markdown. | |
| 384 String comment; | |
| 385 | |
| 386 /// List of the names of interfaces that this class implements. | |
| 387 List<String> interfaces; | |
| 388 | |
| 389 /// Top-level variables in the class. | |
| 390 Map<String, Variable> variables; | |
| 391 | |
| 392 /// Methods in the class. | |
| 393 Map<String, Method> methods; | |
| 394 | |
| 395 String name; | |
| 396 String superclass; | |
| 397 bool isAbstract; | |
| 398 bool isTypedef; | |
| 399 | |
| 400 Class(this.name, this.superclass, this.isAbstract, this.isTypedef, | |
| 401 this.comment, this.interfaces, this.variables, this.methods, this.id); | |
| 402 | |
| 403 /// Generates a map describing the [Class] object. | |
| 404 Map toMap() { | |
| 405 var classMap = {}; | |
| 406 classMap["id"] = id; | |
| 407 classMap["name"] = name; | |
| 408 classMap["comment"] = comment; | |
| 409 classMap["superclass"] = superclass; | |
| 410 classMap["abstract"] = isAbstract.toString(); | |
| 411 classMap["typedef"] = isTypedef.toString(); | |
| 412 classMap["implements"] = new List.from(interfaces); | |
| 413 classMap["variables"] = recurseMap(variables); | |
| 414 classMap["methods"] = recurseMap(methods); | |
| 415 return classMap; | |
| 416 } | |
| 417 } | |
| 418 | |
| 419 /** | |
| 420 * A class containing properties of a Dart variable. | |
| 421 */ | |
| 422 class Variable { | |
| 423 | |
| 424 /// Unique ID number for resolving links. | |
| 425 int id; | |
| 426 | |
| 427 /// Documentation comment with converted markdown. | |
| 428 String comment; | |
| 429 | |
| 430 String name; | |
| 431 bool isFinal; | |
| 432 bool isStatic; | |
| 433 String type; | |
| 434 | |
| 435 Variable(this.name, this.isFinal, this.isStatic, this.type, | |
| 436 this.comment, this.id); | |
| 437 | |
| 438 /// Generates a map describing the [Variable] object. | |
| 439 Map toMap() { | |
| 440 var variableMap = {}; | |
| 441 variableMap["id"] = id; | |
| 442 variableMap["name"] = name; | |
| 443 variableMap["comment"] = comment; | |
| 444 variableMap["final"] = isFinal.toString(); | |
| 445 variableMap["static"] = isStatic.toString(); | |
| 446 variableMap["type"] = type; | |
| 447 return variableMap; | |
| 448 } | |
| 449 } | |
| 450 | |
| 451 /** | |
| 452 * A class containing properties of a Dart method. | |
| 453 */ | |
| 454 class Method { | |
| 455 | |
| 456 /// Unique ID number for resolving links. | |
| 457 int id; | |
| 458 | |
| 459 /// Documentation comment with converted markdown. | |
| 460 String comment; | |
| 461 | |
| 462 /// Parameters for this method. | |
| 463 Map<String, Parameter> parameters; | |
| 464 | |
| 465 String name; | |
| 466 bool isSetter; | |
| 467 bool isGetter; | |
| 468 bool isConstructor; | |
| 469 bool isOperator; | |
| 470 bool isStatic; | |
| 471 String returnType; | |
| 472 | |
| 473 Method(this.name, this.isSetter, this.isGetter, this.isConstructor, | |
| 474 this.isOperator, this.isStatic, this.returnType, this.comment, | |
| 475 this.parameters, this.id); | |
| 476 | |
| 477 /// Generates a map describing the [Method] object. | |
| 478 Map toMap() { | |
| 479 var methodMap = {}; | |
| 480 methodMap["id"] = id; | |
| 481 methodMap["name"] = name; | |
| 482 methodMap["comment"] = comment; | |
| 483 methodMap["type"] = isSetter ? "setter" : isGetter ? "getter" : | |
| 484 isOperator ? "operator" : isConstructor ? "constructor" : "method"; | |
| 485 methodMap["static"] = isStatic.toString(); | |
| 486 methodMap["return"] = returnType; | |
| 487 methodMap["parameters"] = recurseMap(parameters); | |
| 488 return methodMap; | |
| 489 } | |
| 490 } | |
| 491 | |
| 492 /** | |
| 493 * A class containing properties of a Dart method/function parameter. | |
| 494 */ | |
| 495 class Parameter { | |
| 496 | |
| 497 /// Unique ID number for resolving links. | |
| 498 int id; | |
| 499 | |
| 500 String name; | |
| 501 bool isOptional; | |
| 502 bool isNamed; | |
| 503 bool hasDefaultValue; | |
| 504 String type; | |
| 505 String defaultValue; | |
| 506 | |
| 507 Parameter(this.name, this.isOptional, this.isNamed, this.hasDefaultValue, | |
| 508 this.type, this.defaultValue, this.id); | |
| 509 | |
| 510 /// Generates a map describing the [Parameter] object. | |
| 511 Map toMap() { | |
| 512 var parameterMap = {}; | |
| 513 parameterMap["id"] = id; | |
| 514 parameterMap["name"] = name; | |
| 515 parameterMap["optional"] = isOptional.toString(); | |
| 516 parameterMap["named"] = isNamed.toString(); | |
| 517 parameterMap["default"] = hasDefaultValue.toString(); | |
| 518 parameterMap["type"] = type; | |
| 519 parameterMap["value"] = defaultValue; | |
| 520 return parameterMap; | |
| 521 } | |
| 522 } | |
| 523 | |
| 524 /** | |
| 525 * Writes text to a file in the 'docs' directory. | |
| 526 */ | |
| 527 void _writeToFile(String text, String filename) { | |
| 528 Directory dir = new Directory('docs'); | |
| 529 if (!dir.existsSync()) { | |
| 530 dir.createSync(); | |
| 531 } | |
| 532 File file = new File('docs/$filename'); | |
| 533 if (!file.existsSync()) { | |
| 534 file.createSync(); | |
| 535 } | |
| 536 file.openSync(); | |
| 537 file.writeAsString(text); | |
| 538 } | |
| OLD | NEW |