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