| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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 /// **docgen** is a tool for creating machine readable representations of Dart | 5 /// **docgen** is a tool for creating machine readable representations of Dart |
| 6 /// code metadata, including: classes, members, comments and annotations. | 6 /// code metadata, including: classes, members, comments and annotations. |
| 7 /// | 7 /// |
| 8 /// docgen is run on a `.dart` file or a directory containing `.dart` files. | 8 /// docgen is run on a `.dart` file or a directory containing `.dart` files. |
| 9 /// | 9 /// |
| 10 /// $ dart docgen.dart [OPTIONS] [FILE/DIR] | 10 /// $ dart docgen.dart [OPTIONS] [FILE/DIR] |
| (...skipping 16 matching lines...) Expand all Loading... |
| 27 import '../../../sdk/lib/_internal/compiler/compiler.dart' as api; | 27 import '../../../sdk/lib/_internal/compiler/compiler.dart' as api; |
| 28 import '../../../sdk/lib/_internal/compiler/implementation/filenames.dart'; | 28 import '../../../sdk/lib/_internal/compiler/implementation/filenames.dart'; |
| 29 import '../../../sdk/lib/_internal/compiler/implementation/mirrors/dart2js_mirro
r.dart' | 29 import '../../../sdk/lib/_internal/compiler/implementation/mirrors/dart2js_mirro
r.dart' |
| 30 as dart2js; | 30 as dart2js; |
| 31 import '../../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors.dart'
; | 31 import '../../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors.dart'
; |
| 32 import '../../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors_util.
dart' | 32 import '../../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors_util.
dart' |
| 33 as dart2js_util; | 33 as dart2js_util; |
| 34 import '../../../sdk/lib/_internal/compiler/implementation/source_file_provider.
dart'; | 34 import '../../../sdk/lib/_internal/compiler/implementation/source_file_provider.
dart'; |
| 35 import '../../../sdk/lib/_internal/libraries.dart'; | 35 import '../../../sdk/lib/_internal/libraries.dart'; |
| 36 | 36 |
| 37 var logger = new Logger('Docgen'); | 37 const _DEFAULT_OUTPUT_DIRECTORY = 'docs'; |
| 38 | 38 |
| 39 const DEFAULT_OUTPUT_DIRECTORY = 'docs'; | 39 /// Annotations that we do not display in the viewer. |
| 40 | 40 const List<String> _SKIPPED_ANNOTATIONS = const [ |
| 41 var _outputDirectory; | |
| 42 | |
| 43 const String USAGE = 'Usage: dart docgen.dart [OPTIONS] fooDir/barFile'; | |
| 44 | |
| 45 | |
| 46 List<String> skippedAnnotations = const [ | |
| 47 'metadata.DocsEditable', '_js_helper.JSName', '_js_helper.Creates', | 41 'metadata.DocsEditable', '_js_helper.JSName', '_js_helper.Creates', |
| 48 '_js_helper.Returns']; | 42 '_js_helper.Returns']; |
| 49 | 43 |
| 50 /// Set of libraries declared in the SDK, so libraries that can be accessed | |
| 51 /// when running dart by default. | |
| 52 Iterable<LibraryMirror> _sdkLibraries; | |
| 53 | |
| 54 /// The dart:core library, which contains all types that are always available | |
| 55 /// without import. | |
| 56 LibraryMirror _coreLibrary; | |
| 57 | |
| 58 /// Support for [:foo:]-style code comments to the markdown parser. | 44 /// Support for [:foo:]-style code comments to the markdown parser. |
| 59 List<markdown.InlineSyntax> markdownSyntaxes = | 45 List<markdown.InlineSyntax> _MARKDOWN_SYNTAXES = |
| 60 [new markdown.CodeSyntax(r'\[:\s?((?:.|\n)*?)\s?:\]')]; | 46 [new markdown.CodeSyntax(r'\[:\s?((?:.|\n)*?)\s?:\]')]; |
| 61 | 47 |
| 48 // TODO(efortuna): The use of this field is odd (this is based on how it was |
| 49 // originally used. Try to cleanup. |
| 62 /// Index of all indexable items. This also ensures that no class is | 50 /// Index of all indexable items. This also ensures that no class is |
| 63 /// created more than once. | 51 /// created more than once. |
| 64 Map<String, Indexable> entityMap = new Map<String, Indexable>(); | 52 Map<String, Indexable> entityMap = new Map<String, Indexable>(); |
| 65 | 53 |
| 66 /// This is set from the command line arguments flag --include-private | 54 /// Index of all the dart2js mirrors examined to corresponding MirrorBased |
| 67 bool _includePrivate = false; | 55 /// docgen objects. |
| 56 /// |
| 57 /// Used for lookup because of the dart2js mirrors exports |
| 58 /// issue. The second level map is indexed by owner docName for faster lookup. |
| 59 /// Why two levels of lookup? Speed, man. Speed. |
| 60 Map<String, Map<String, Set<MirrorBased>>> mirrorToDocgen = |
| 61 new Map<String, Map<String, Set<MirrorBased>>>(); |
| 68 | 62 |
| 69 /// This is set from the command line flag --include-dependent-packages | 63 /// Given a Dart2jsMirror, find the corresponding Docgen [MirrorBased] object. |
| 70 bool _includeDependentPackages = false; | 64 /// |
| 65 /// We have this global lookup function to avoid re-implementing looking up the |
| 66 /// scoping rules for comment resolution here (it is currently done in mirrors). |
| 67 /// If no corresponding MirrorBased object is found, we return a [DummyMirror] |
| 68 /// that simply returns the original mirror's qualifiedName while behaving like |
| 69 /// a MirrorBased object. |
| 70 MirrorBased getDocgenObject(DeclarationMirror mirror, [Indexable owner]) { |
| 71 Map<String, Set<MirrorBased>> docgenObj = |
| 72 mirrorToDocgen[mirror.qualifiedName]; |
| 73 if (docgenObj == null) { |
| 74 return new DummyMirror(mirror, owner); |
| 75 } |
| 71 | 76 |
| 72 /// Library names to explicitly exclude. | 77 Set<MirrorBased> results = new Set<MirrorBased>(); |
| 73 /// | 78 var setToExamine = new Set(); |
| 74 /// Set from the command line option | 79 if (owner != null) { |
| 75 /// --exclude-lib. | 80 var firstSet = docgenObj[owner.docName]; |
| 76 List<String> _excluded; | 81 if (firstSet != null) setToExamine.addAll(firstSet); |
| 82 if (Indexable._coreLibrary != null && |
| 83 docgenObj[Indexable._coreLibrary.docName] != null) { |
| 84 setToExamine.addAll(docgenObj[Indexable._coreLibrary.docName]); |
| 85 } |
| 86 } else { |
| 87 for (var value in docgenObj.values) { |
| 88 setToExamine.addAll(value); |
| 89 } |
| 90 } |
| 77 | 91 |
| 78 // TODO(janicejl): Make MDN content generic or pluggable. Maybe move | 92 for(MirrorBased mirrorBased in setToExamine) { |
| 79 // MDN-specific code to its own library that is imported into the default impl? | 93 // This check makes me sad, but Dart doesn't seem to be dynamic enough to |
| 80 /// Map of all the comments for dom elements from MDN. | 94 // write this another way. Dart2js and the editor didn't like the Type |
| 81 Map _mdn; | 95 // lookup in a map. |
| 96 if (mirrorBased.mirror.qualifiedName == mirror.qualifiedName && |
| 97 ((mirror is ClassMirror && mirrorBased is Class) |
| 98 || (mirror is LibraryMirror && mirrorBased is Library) |
| 99 || (mirror is MethodMirror && mirrorBased is Method) |
| 100 || (mirror is VariableMirror && mirrorBased is Variable) |
| 101 || (mirror is TypedefMirror && mirrorBased is Typedef) |
| 102 || (mirror is TypeMirror && mirrorBased is Type) |
| 103 || (mirror is InstanceMirror && mirrorBased is Annotation))) { |
| 104 results.add(mirrorBased); |
| 105 } |
| 106 } |
| 107 |
| 108 if (results.length > 0) { |
| 109 return results.first; // This might occur if we didn't specify an "owner." |
| 110 } |
| 111 return new DummyMirror(mirror, owner); |
| 112 } |
| 113 |
| 114 /// For types that we do not explicitly create or have not yet created in our |
| 115 /// entity map (like core types). |
| 116 class DummyMirror implements MirrorBased { |
| 117 DeclarationMirror mirror; |
| 118 /// The library that contains this element, if any. Used as a hint to help |
| 119 /// determine which object we're referring to when looking up this mirror in |
| 120 /// our map. |
| 121 Indexable owner; |
| 122 DummyMirror(this.mirror, [this.owner]); |
| 123 |
| 124 String get docName { |
| 125 if (mirror == null) return ''; |
| 126 if (mirror is LibraryMirror) { |
| 127 return mirror.qualifiedName.replaceAll('.','-'); |
| 128 } |
| 129 var mirrorOwner = mirror.owner; |
| 130 if (mirrorOwner == null) return mirror.qualifiedName; |
| 131 var simpleName = mirror.simpleName; |
| 132 if (mirror is MethodMirror && (mirror as MethodMirror).isConstructor) { |
| 133 // We name constructors specially -- including the class name again and a |
| 134 // "-" to separate the constructor from its name (if any). |
| 135 simpleName = '${mirrorOwner.simpleName}-$simpleName'; |
| 136 } |
| 137 return getDocgenObject(mirrorOwner, owner).docName + '.' + simpleName; |
| 138 } |
| 139 List<Annotation> _createAnnotations(DeclarationMirror mirror, |
| 140 MirrorBased owner) => null; |
| 141 |
| 142 bool get isPrivate => mirror == null? false : mirror.isPrivate; |
| 143 } |
| 144 |
| 145 abstract class MirrorBased { |
| 146 DeclarationMirror get mirror; |
| 147 MirrorBased owner; |
| 148 |
| 149 /// Returns this object's qualified name, but following the conventions |
| 150 /// we're using in Dartdoc, which is that library names with dots in them |
| 151 /// have them replaced with hyphens. |
| 152 String get docName => owner.docName + '.' + mirror.simpleName; |
| 153 |
| 154 /// Returns a list of meta annotations assocated with a mirror. |
| 155 List<Annotation> _createAnnotations(DeclarationMirror mirror, |
| 156 MirrorBased owner) { |
| 157 var annotationMirrors = mirror.metadata.where((e) => |
| 158 e is dart2js.Dart2JsConstructedConstantMirror); |
| 159 var annotations = []; |
| 160 annotationMirrors.forEach((annotation) { |
| 161 var docgenAnnotation = new Annotation(annotation, owner); |
| 162 if (!_SKIPPED_ANNOTATIONS.contains( |
| 163 docgenAnnotation.mirror.qualifiedName)) { |
| 164 annotations.add(docgenAnnotation); |
| 165 } |
| 166 }); |
| 167 return annotations; |
| 168 } |
| 169 |
| 170 bool get isPrivate => false; |
| 171 } |
| 82 | 172 |
| 83 /// Docgen constructor initializes the link resolver for markdown parsing. | 173 /// Docgen constructor initializes the link resolver for markdown parsing. |
| 84 /// Also initializes the command line arguments. | 174 /// Also initializes the command line arguments. |
| 85 /// | 175 /// |
| 86 /// [packageRoot] is the packages directory of the directory being analyzed. | 176 /// [packageRoot] is the packages directory of the directory being analyzed. |
| 87 /// If [includeSdk] is `true`, then any SDK libraries explicitly imported will | 177 /// If [includeSdk] is `true`, then any SDK libraries explicitly imported will |
| 88 /// also be documented. | 178 /// also be documented. |
| 89 /// If [parseSdk] is `true`, then all Dart SDK libraries will be documented. | 179 /// If [parseSdk] is `true`, then all Dart SDK libraries will be documented. |
| 90 /// This option is useful when only the SDK libraries are needed. | 180 /// This option is useful when only the SDK libraries are needed. |
| 91 /// | 181 /// |
| 92 /// Returned Future completes with true if document generation is successful. | 182 /// Returned Future completes with true if document generation is successful. |
| 93 Future<bool> docgen(List<String> files, {String packageRoot, | 183 Future<bool> docgen(List<String> files, {String packageRoot, |
| 94 bool outputToYaml: true, bool includePrivate: false, bool includeSdk: false, | 184 bool outputToYaml: true, bool includePrivate: false, bool includeSdk: false, |
| 95 bool parseSdk: false, bool append: false, String introduction: '', | 185 bool parseSdk: false, bool append: false, String introduction: '', |
| 96 out: DEFAULT_OUTPUT_DIRECTORY, List<String> excludeLibraries, | 186 out: _DEFAULT_OUTPUT_DIRECTORY, List<String> excludeLibraries : const [], |
| 97 bool includeDependentPackages}) { | 187 bool includeDependentPackages: false}) { |
| 98 _excluded = excludeLibraries; | 188 return _Generator.generateDocumentation(files, packageRoot: packageRoot, |
| 99 _includePrivate = includePrivate; | 189 outputToYaml: outputToYaml, includePrivate: includePrivate, |
| 100 _outputDirectory = out; | 190 includeSdk: includeSdk, parseSdk: parseSdk, append: append, |
| 101 _includeDependentPackages = includeDependentPackages; | 191 introduction: introduction, out: out, excludeLibraries: excludeLibraries, |
| 102 if (!append) { | 192 includeDependentPackages: includeDependentPackages); |
| 103 var dir = new Directory(_outputDirectory); | |
| 104 if (dir.existsSync()) dir.deleteSync(recursive: true); | |
| 105 } | |
| 106 | |
| 107 if (packageRoot == null && !parseSdk) { | |
| 108 var type = FileSystemEntity.typeSync(files.first); | |
| 109 if (type == FileSystemEntityType.DIRECTORY) { | |
| 110 packageRoot = _findPackageRoot(files.first); | |
| 111 } else if (type == FileSystemEntityType.FILE) { | |
| 112 logger.warning('WARNING: No package root defined. If Docgen fails, try ' | |
| 113 'again by setting the --package-root option.'); | |
| 114 } | |
| 115 } | |
| 116 logger.info('Package Root: ${packageRoot}'); | |
| 117 if (_includeDependentPackages) { | |
| 118 files.addAll(allDependentPackageDirs(files.first)); | |
| 119 } | |
| 120 var requestedLibraries = _listLibraries(files); | |
| 121 var allLibraries = []..addAll(requestedLibraries); | |
| 122 if (includeSdk) { | |
| 123 allLibraries.addAll(_listSdk()); | |
| 124 } | |
| 125 | |
| 126 return getMirrorSystem(allLibraries, packageRoot: packageRoot, | |
| 127 parseSdk: parseSdk) | |
| 128 .then((MirrorSystem mirrorSystem) { | |
| 129 if (mirrorSystem.libraries.isEmpty) { | |
| 130 throw new StateError('No library mirrors were created.'); | |
| 131 } | |
| 132 var availableLibraries = mirrorSystem.libraries.values.where( | |
| 133 (each) => each.uri.scheme == 'file'); | |
| 134 _sdkLibraries = mirrorSystem.libraries.values.where( | |
| 135 (each) => each.uri.scheme == 'dart'); | |
| 136 _coreLibrary = _sdkLibraries.singleWhere((lib) => | |
| 137 lib.uri.toString().startsWith('dart:core')); | |
| 138 var availableLibrariesByPath = new Map.fromIterables( | |
| 139 availableLibraries.map((each) => each.uri), | |
| 140 availableLibraries); | |
| 141 var librariesToDocument = requestedLibraries.map( | |
| 142 (each) => availableLibrariesByPath.putIfAbsent(each, | |
| 143 () => throw "Missing library $each")).toList(); | |
| 144 librariesToDocument.addAll((includeSdk || parseSdk) ? _sdkLibraries : []); | |
| 145 librariesToDocument.removeWhere((x) => _excluded.contains(x.simpleName)); | |
| 146 _documentLibraries(librariesToDocument, includeSdk: includeSdk, | |
| 147 outputToYaml: outputToYaml, append: append, parseSdk: parseSdk, | |
| 148 introduction: introduction); | |
| 149 return true; | |
| 150 }); | |
| 151 } | |
| 152 | |
| 153 /// All of the directories for our dependent packages | |
| 154 List<String> allDependentPackageDirs(String packageDirectory) { | |
| 155 var dependentsJson = Process.runSync('pub', ['list-package-dirs'], | |
| 156 workingDirectory: packageDirectory, runInShell: true); | |
| 157 if (dependentsJson.exitCode != 0) { | |
| 158 print(dependentsJson.stderr); | |
| 159 } | |
| 160 var dependents = JSON.decode(dependentsJson.stdout)['packages']; | |
| 161 return dependents.values.toList(); | |
| 162 } | |
| 163 | |
| 164 /// For a library's [mirror], determine the name of the package (if any) we | |
| 165 /// believe it came from (because of its file URI). | |
| 166 /// | |
| 167 /// If [library] is specified, we set the packageName field. If no package could | |
| 168 /// be determined, we return an empty string. | |
| 169 String _findPackage(LibraryMirror mirror, [Library library]) { | |
| 170 if (mirror == null) return ''; | |
| 171 if (library == null) { | |
| 172 library = entityMap[docName(mirror)]; | |
| 173 } | |
| 174 if (library != null) { | |
| 175 if (library.hasBeenCheckedForPackage) return library.packageName; | |
| 176 library.hasBeenCheckedForPackage = true; | |
| 177 } | |
| 178 if (mirror.uri.scheme != 'file') return ''; | |
| 179 var filePath = mirror.uri.toFilePath(); | |
| 180 // We assume that we are documenting only libraries under package/lib | |
| 181 var rootdir = path.dirname((path.dirname(filePath))); | |
| 182 var pubspec = path.join(rootdir, 'pubspec.yaml'); | |
| 183 var packageName = _packageName(pubspec); | |
| 184 if (library != null) { | |
| 185 library.packageName = packageName; | |
| 186 // Associate the package readme with all the libraries. This is a bit | |
| 187 // wasteful, but easier than trying to figure out which partial match | |
| 188 // is best. | |
| 189 library.packageIntro = _packageIntro(rootdir); | |
| 190 } | |
| 191 return packageName; | |
| 192 } | |
| 193 | |
| 194 String _packageIntro(packageDir) { | |
| 195 var dir = new Directory(packageDir); | |
| 196 var files = dir.listSync(); | |
| 197 var readmes = files.where((FileSystemEntity each) => (each is File && | |
| 198 each.path.substring(packageDir.length + 1, each.path.length) | |
| 199 .startsWith('README'))).toList(); | |
| 200 if (readmes.isEmpty) return ''; | |
| 201 // If there are multiples, pick the shortest name. | |
| 202 readmes.sort((a, b) => a.path.length.compareTo(b.path.length)); | |
| 203 var readme = readmes.first; | |
| 204 var linkResolver = (name) => fixReference(name, null, null, null); | |
| 205 var contents = markdown.markdownToHtml(readme | |
| 206 .readAsStringSync(), linkResolver: linkResolver, | |
| 207 inlineSyntaxes: markdownSyntaxes); | |
| 208 return contents; | |
| 209 } | |
| 210 | |
| 211 List<Uri> _listLibraries(List<String> args) { | |
| 212 var libraries = new List<Uri>(); | |
| 213 for (var arg in args) { | |
| 214 var type = FileSystemEntity.typeSync(arg); | |
| 215 | |
| 216 if (type == FileSystemEntityType.FILE) { | |
| 217 if (arg.endsWith('.dart')) { | |
| 218 libraries.add(new Uri.file(path.absolute(arg))); | |
| 219 logger.info('Added to libraries: ${libraries.last}'); | |
| 220 } | |
| 221 } else { | |
| 222 libraries.addAll(_listDartFromDir(arg)); | |
| 223 } | |
| 224 } | |
| 225 return libraries; | |
| 226 } | |
| 227 | |
| 228 List<Uri> _listDartFromDir(String args) { | |
| 229 var libraries = []; | |
| 230 // To avoid anaylzing package files twice, only files with paths not | |
| 231 // containing '/packages' will be added. The only exception is if the file to | |
| 232 // analyze already has a '/package' in its path. | |
| 233 var files = listDir(args, recursive: true).where((f) => f.endsWith('.dart') && | |
| 234 (!f.contains('${path.separator}packages') || | |
| 235 args.contains('${path.separator}packages'))).toList(); | |
| 236 | |
| 237 files.forEach((String f) { | |
| 238 // Only include libraries at the top level of "lib" | |
| 239 if (path.basename(path.dirname(f)) == 'lib') { | |
| 240 // Only add the file if it does not contain 'part of' | |
| 241 // TODO(janicejl): Remove when Issue(12406) is resolved. | |
| 242 var contents = new File(f).readAsStringSync(); | |
| 243 if (!(contents.contains(new RegExp('\npart of ')) || | |
| 244 contents.startsWith(new RegExp('part of ')))) { | |
| 245 libraries.add(new Uri.file(path.normalize(path.absolute(f)))); | |
| 246 logger.info('Added to libraries: $f'); | |
| 247 } | |
| 248 } | |
| 249 }); | |
| 250 return libraries; | |
| 251 } | |
| 252 | |
| 253 String _findPackageRoot(String directory) { | |
| 254 var files = listDir(directory, recursive: true); | |
| 255 // Return '' means that there was no pubspec.yaml and therefor no packageRoot. | |
| 256 String packageRoot = files.firstWhere((f) => | |
| 257 f.endsWith('${path.separator}pubspec.yaml'), orElse: () => ''); | |
| 258 if (packageRoot != '') { | |
| 259 packageRoot = path.join(path.dirname(packageRoot), 'packages'); | |
| 260 } | |
| 261 return packageRoot; | |
| 262 } | |
| 263 | |
| 264 /// Read a pubspec and return the library name. | |
| 265 String _packageName(String pubspecName) { | |
| 266 File pubspec = new File(pubspecName); | |
| 267 if (!pubspec.existsSync()) return ''; | |
| 268 var contents = pubspec.readAsStringSync(); | |
| 269 var spec = loadYaml(contents); | |
| 270 return spec["name"]; | |
| 271 } | |
| 272 | |
| 273 List<Uri> _listSdk() { | |
| 274 var sdk = new List<Uri>(); | |
| 275 LIBRARIES.forEach((String name, LibraryInfo info) { | |
| 276 if (info.documented) { | |
| 277 sdk.add(Uri.parse('dart:$name')); | |
| 278 logger.info('Add to SDK: ${sdk.last}'); | |
| 279 } | |
| 280 }); | |
| 281 return sdk; | |
| 282 } | 193 } |
| 283 | 194 |
| 284 /// Analyzes set of libraries by getting a mirror system and triggers the | 195 /// Analyzes set of libraries by getting a mirror system and triggers the |
| 285 /// documentation of the libraries. | 196 /// documentation of the libraries. |
| 286 Future<MirrorSystem> getMirrorSystem(List<Uri> libraries, | 197 Future<MirrorSystem> getMirrorSystem(List<Uri> libraries, |
| 287 {String packageRoot, bool parseSdk: false}) { | 198 {String packageRoot, bool parseSdk: false}) { |
| 288 if (libraries.isEmpty) throw new StateError('No Libraries.'); | 199 if (libraries.isEmpty) throw new StateError('No Libraries.'); |
| 289 // Finds the root of SDK library based off the location of docgen. | 200 // Finds the root of SDK library based off the location of docgen. |
| 290 | 201 |
| 291 var root = findRootDirectory(); | 202 var root = _Generator._rootDirectory; |
| 292 var sdkRoot = path.normalize(path.absolute(path.join(root, 'sdk'))); | 203 var sdkRoot = path.normalize(path.absolute(path.join(root, 'sdk'))); |
| 293 logger.info('SDK Root: ${sdkRoot}'); | 204 _Generator.logger.info('SDK Root: ${sdkRoot}'); |
| 294 return _analyzeLibraries(libraries, sdkRoot, packageRoot: packageRoot); | 205 return _Generator._analyzeLibraries(libraries, sdkRoot, |
| 206 packageRoot: packageRoot); |
| 295 } | 207 } |
| 296 | 208 |
| 297 String findRootDirectory() { | 209 class _Generator { |
| 298 var scriptDir = path.absolute(path.dirname(Platform.script.toFilePath())); | 210 static var _outputDirectory; |
| 299 var root = scriptDir; | 211 |
| 300 while(path.basename(root) != 'dart') { | 212 /// This is set from the command line arguments flag --include-private |
| 301 root = path.dirname(root); | 213 static bool _includePrivate = false; |
| 302 } | 214 |
| 303 return root; | 215 /// Library names to explicitly exclude. |
| 216 /// |
| 217 /// Set from the command line option |
| 218 /// --exclude-lib. |
| 219 static List<String> _excluded; |
| 220 |
| 221 static Logger logger = new Logger('Docgen'); |
| 222 |
| 223 /// Docgen constructor initializes the link resolver for markdown parsing. |
| 224 /// Also initializes the command line arguments. |
| 225 /// |
| 226 /// [packageRoot] is the packages directory of the directory being analyzed. |
| 227 /// If [includeSdk] is `true`, then any SDK libraries explicitly imported will |
| 228 /// also be documented. |
| 229 /// If [parseSdk] is `true`, then all Dart SDK libraries will be documented. |
| 230 /// This option is useful when only the SDK libraries are needed. |
| 231 /// |
| 232 /// Returned Future completes with true if document generation is successful. |
| 233 static Future<bool> generateDocumentation(List<String> files, |
| 234 {String packageRoot, bool outputToYaml: true, bool includePrivate: false, |
| 235 bool includeSdk: false, bool parseSdk: false, bool append: false, |
| 236 String introduction: '', out: _DEFAULT_OUTPUT_DIRECTORY, |
| 237 List<String> excludeLibraries : const [], |
| 238 bool includeDependentPackages: false}) { |
| 239 _excluded = excludeLibraries; |
| 240 _includePrivate = includePrivate; |
| 241 logger.onRecord.listen((record) => print(record.message)); |
| 242 |
| 243 _ensureOutputDirectory(out, append); |
| 244 var updatedPackageRoot = _obtainPackageRoot(packageRoot, parseSdk, files); |
| 245 |
| 246 var requestedLibraries = _findLibrariesToDocument(files, |
| 247 includeDependentPackages); |
| 248 |
| 249 var allLibraries = []..addAll(requestedLibraries); |
| 250 if (includeSdk) { |
| 251 allLibraries.addAll(_listSdk()); |
| 252 } |
| 253 |
| 254 return getMirrorSystem(allLibraries, packageRoot: updatedPackageRoot, |
| 255 parseSdk: parseSdk) |
| 256 .then((MirrorSystem mirrorSystem) { |
| 257 if (mirrorSystem.libraries.isEmpty) { |
| 258 throw new StateError('No library mirrors were created.'); |
| 259 } |
| 260 Indexable.initializeTopLevelLibraries(mirrorSystem); |
| 261 |
| 262 var availableLibraries = mirrorSystem.libraries.values.where( |
| 263 (each) => each.uri.scheme == 'file'); |
| 264 var availableLibrariesByPath = new Map.fromIterables( |
| 265 availableLibraries.map((each) => each.uri), |
| 266 availableLibraries); |
| 267 var librariesToDocument = requestedLibraries.map( |
| 268 (each) => availableLibrariesByPath.putIfAbsent(each, |
| 269 () => throw "Missing library $each")).toList(); |
| 270 librariesToDocument.addAll( |
| 271 (includeSdk || parseSdk) ? Indexable._sdkLibraries : []); |
| 272 librariesToDocument.removeWhere( |
| 273 (x) => _excluded.contains(x.simpleName)); |
| 274 _documentLibraries(librariesToDocument, includeSdk: includeSdk, |
| 275 outputToYaml: outputToYaml, append: append, parseSdk: parseSdk, |
| 276 introduction: introduction); |
| 277 return true; |
| 278 }); |
| 279 } |
| 280 |
| 281 /// Writes text to a file in the output directory. |
| 282 static void _writeToFile(String text, String filename, {bool append: false}) { |
| 283 if (text == null) return; |
| 284 Directory dir = new Directory(_outputDirectory); |
| 285 if (!dir.existsSync()) { |
| 286 dir.createSync(); |
| 287 } |
| 288 if (path.split(filename).length > 1) { |
| 289 var splitList = path.split(filename); |
| 290 for (int i = 0; i < splitList.length; i++) { |
| 291 var level = splitList[i]; |
| 292 } |
| 293 for (var level in path.split(filename)) { |
| 294 var subdir = new Directory(path.join(_outputDirectory, |
| 295 path.dirname(filename))); |
| 296 if (!subdir.existsSync()) { |
| 297 subdir.createSync(); |
| 298 } |
| 299 } |
| 300 } |
| 301 File file = new File(path.join(_outputDirectory, filename)); |
| 302 file.writeAsStringSync(text, |
| 303 mode: append ? FileMode.APPEND : FileMode.WRITE); |
| 304 } |
| 305 |
| 306 /// Creates documentation for filtered libraries. |
| 307 static void _documentLibraries(List<LibraryMirror> libs, |
| 308 {bool includeSdk: false, bool outputToYaml: true, bool append: false, |
| 309 bool parseSdk: false, String introduction: ''}) { |
| 310 libs.forEach((lib) { |
| 311 // Files belonging to the SDK have a uri that begins with 'dart:'. |
| 312 if (includeSdk || !lib.uri.toString().startsWith('dart:')) { |
| 313 var library = generateLibrary(lib); |
| 314 entityMap[library.name] = library; |
| 315 } |
| 316 }); |
| 317 |
| 318 var filteredEntities = entityMap.values.where(_isFullChainVisible); |
| 319 |
| 320 /*var filteredEntities2 = new Set<MirrorBased>(); |
| 321 for (Map<String, Set<MirrorBased>> firstLevel in mirrorToDocgen.values) { |
| 322 for (Set<MirrorBased> items in firstLevel.values) { |
| 323 for (MirrorBased item in items) { |
| 324 if (_isFullChainVisible(item)) { |
| 325 filteredEntities2.add(item); |
| 326 } |
| 327 } |
| 328 } |
| 329 }*/ |
| 330 |
| 331 /*print('THHHHHEEE DIFFERENCE IS'); |
| 332 var set1 = new Set.from(filteredEntities); |
| 333 var set2 = new Set.from(filteredEntities2); |
| 334 var aResult = set2.difference(set1); |
| 335 for (MirrorBased r in aResult) { |
| 336 print(' a result is $r and ${r.docName}'); |
| 337 }*/ |
| 338 //print(set1.difference(set2)); |
| 339 |
| 340 // Outputs a JSON file with all libraries and their preview comments. |
| 341 // This will help the viewer know what libraries are available to read in. |
| 342 var libraryMap; |
| 343 var linkResolver = (name) => Indexable.globalFixReference(name); |
| 344 if (append) { |
| 345 var docsDir = listDir(_outputDirectory); |
| 346 if (!docsDir.contains('$_outputDirectory/library_list.json')) { |
| 347 throw new StateError('No library_list.json'); |
| 348 } |
| 349 libraryMap = |
| 350 JSON.decode(new File( |
| 351 '$_outputDirectory/library_list.json').readAsStringSync()); |
| 352 libraryMap['libraries'].addAll(filteredEntities |
| 353 .where((e) => e is Library) |
| 354 .map((e) => e.previewMap)); |
| 355 if (introduction.isNotEmpty) { |
| 356 var intro = libraryMap['introduction']; |
| 357 if (intro.isNotEmpty) intro += '<br/><br/>'; |
| 358 intro += markdown.markdownToHtml( |
| 359 new File(introduction).readAsStringSync(), |
| 360 linkResolver: linkResolver, inlineSyntaxes: _MARKDOWN_SYNTAXES); |
| 361 libraryMap['introduction'] = intro; |
| 362 } |
| 363 outputToYaml = libraryMap['filetype'] == 'yaml'; |
| 364 } else { |
| 365 libraryMap = { |
| 366 'libraries' : filteredEntities.where((e) => |
| 367 e is Library).map((e) => e.previewMap).toList(), |
| 368 'introduction' : introduction == '' ? |
| 369 '' : markdown.markdownToHtml(new File(introduction) |
| 370 .readAsStringSync(), linkResolver: linkResolver, |
| 371 inlineSyntaxes: _MARKDOWN_SYNTAXES), |
| 372 'filetype' : outputToYaml ? 'yaml' : 'json' |
| 373 }; |
| 374 } |
| 375 _writeToFile(JSON.encode(libraryMap), 'library_list.json'); |
| 376 |
| 377 // Output libraries and classes to file after all information is generated. |
| 378 filteredEntities.where((e) => e is Class || e is Library).forEach((output) { |
| 379 _writeIndexableToFile(output, outputToYaml); |
| 380 }); |
| 381 |
| 382 // Outputs all the qualified names documented with their type. |
| 383 // This will help generate search results. |
| 384 _writeToFile(filteredEntities.map((e) => |
| 385 '${e.qualifiedName} ${e.typeName}').join('\n') + '\n', |
| 386 'index.txt', append: append); |
| 387 var index = new Map.fromIterables( |
| 388 filteredEntities.map((e) => e.qualifiedName), |
| 389 filteredEntities.map((e) => e.typeName)); |
| 390 if (append) { |
| 391 var previousIndex = |
| 392 JSON.decode(new File( |
| 393 '$_outputDirectory/index.json').readAsStringSync()); |
| 394 index.addAll(previousIndex); |
| 395 } |
| 396 _writeToFile(JSON.encode(index), 'index.json'); |
| 397 } |
| 398 |
| 399 static void _writeIndexableToFile(Indexable result, bool outputToYaml) { |
| 400 var outputFile = result.fileName; |
| 401 var output; |
| 402 if (outputToYaml) { |
| 403 output = getYamlString(result.toMap()); |
| 404 outputFile = outputFile + '.yaml'; |
| 405 } else { |
| 406 output = JSON.encode(result.toMap()); |
| 407 outputFile = outputFile + '.json'; |
| 408 } |
| 409 _writeToFile(output, outputFile); |
| 410 } |
| 411 |
| 412 /// Set the location of the ouput directory, and ensure that the location is |
| 413 /// available on the file system. |
| 414 static void _ensureOutputDirectory(String outputDirectory, bool append) { |
| 415 _outputDirectory = outputDirectory; |
| 416 if (!append) { |
| 417 var dir = new Directory(_outputDirectory); |
| 418 if (dir.existsSync()) dir.deleteSync(recursive: true); |
| 419 } |
| 420 } |
| 421 |
| 422 |
| 423 static String get _rootDirectory { |
| 424 var scriptDir = path.absolute(path.dirname(Platform.script.toFilePath())); |
| 425 var root = scriptDir; |
| 426 while(path.basename(root) != 'dart') { |
| 427 root = path.dirname(root); |
| 428 } |
| 429 return root; |
| 430 } |
| 431 |
| 432 /// Analyzes set of libraries and provides a mirror system which can be used |
| 433 /// for static inspection of the source code. |
| 434 static Future<MirrorSystem> _analyzeLibraries(List<Uri> libraries, |
| 435 String libraryRoot, {String packageRoot}) { |
| 436 SourceFileProvider provider = new CompilerSourceFileProvider(); |
| 437 api.DiagnosticHandler diagnosticHandler = |
| 438 (new FormattingDiagnosticHandler(provider) |
| 439 ..showHints = false |
| 440 ..showWarnings = false) |
| 441 .diagnosticHandler; |
| 442 Uri libraryUri = new Uri.file(appendSlash(libraryRoot)); |
| 443 Uri packageUri = null; |
| 444 if (packageRoot != null) { |
| 445 packageUri = new Uri.file(appendSlash(packageRoot)); |
| 446 } |
| 447 return dart2js.analyze(libraries, libraryUri, packageUri, |
| 448 provider.readStringFromUri, diagnosticHandler, |
| 449 ['--preserve-comments', '--categories=Client,Server']) |
| 450 ..catchError((error) { |
| 451 logger.severe('Error: Failed to create mirror system. '); |
| 452 // TODO(janicejl): Use the stack trace package when bug is resolved. |
| 453 // Currently, a string is thrown when it fails to create a mirror |
| 454 // system, and it is not possible to use the stack trace. BUG(#11622) |
| 455 // To avoid printing the stack trace. |
| 456 exit(1); |
| 457 }); |
| 458 } |
| 459 |
| 460 /// For this run of docgen, determine the packageRoot value. |
| 461 /// |
| 462 /// If packageRoot is not explicitly passed, we examine the files we're |
| 463 /// documenting to attempt to find a package root. |
| 464 static String _obtainPackageRoot(String packageRoot, bool parseSdk, |
| 465 List<String> files) { |
| 466 if (packageRoot == null && !parseSdk) { |
| 467 // TODO(efortuna): This logic seems not very robust, but it's from the |
| 468 // original version of the code, pre-refactor, so I'm leavingt it for now. |
| 469 // Revisit to make more robust. |
| 470 var type = FileSystemEntity.typeSync(files.first); |
| 471 if (type == FileSystemEntityType.DIRECTORY) { |
| 472 var files2 = listDir(files.first, recursive: true); |
| 473 // Return '' means that there was no pubspec.yaml and therefor no p |
| 474 // ackageRoot. |
| 475 packageRoot = files2.firstWhere((f) => |
| 476 f.endsWith('${path.separator}pubspec.yaml'), orElse: () => ''); |
| 477 if (packageRoot != '') { |
| 478 packageRoot = path.join(path.dirname(packageRoot), 'packages'); |
| 479 } |
| 480 } else if (type == FileSystemEntityType.FILE) { |
| 481 logger.warning('WARNING: No package root defined. If Docgen fails, try ' |
| 482 'again by setting the --package-root option.'); |
| 483 } |
| 484 } |
| 485 logger.info('Package Root: ${packageRoot}'); |
| 486 return packageRoot; |
| 487 } |
| 488 |
| 489 /// Given the user provided list of items to document, expand all directories |
| 490 /// to document out into specific files and add any dependent packages for |
| 491 /// documentation if desired. |
| 492 static List<Uri> _findLibrariesToDocument(List<String> args, |
| 493 bool includeDependentPackages) { |
| 494 if (includeDependentPackages) { |
| 495 args.addAll(_allDependentPackageDirs(args.first)); |
| 496 } |
| 497 |
| 498 var libraries = new List<Uri>(); |
| 499 for (var arg in args) { |
| 500 if (FileSystemEntity.typeSync(arg) == FileSystemEntityType.FILE) { |
| 501 if (arg.endsWith('.dart')) { |
| 502 libraries.add(new Uri.file(path.absolute(arg))); |
| 503 logger.info('Added to libraries: ${libraries.last}'); |
| 504 } |
| 505 } else { |
| 506 libraries.addAll(_findFilesToDocumentInPackage(arg)); |
| 507 } |
| 508 } |
| 509 return libraries; |
| 510 } |
| 511 |
| 512 /// Given a package name, explore the directory and pull out all top level |
| 513 /// library files in the "lib" directory to document. |
| 514 static List<Uri> _findFilesToDocumentInPackage(String packageName) { |
| 515 var libraries = []; |
| 516 // To avoid anaylzing package files twice, only files with paths not |
| 517 // containing '/packages' will be added. The only exception is if the file |
| 518 // to analyze already has a '/package' in its path. |
| 519 var files = listDir(packageName, recursive: true).where( |
| 520 (f) => f.endsWith('.dart') && (!f.contains('${path.separator}packages') |
| 521 || packageName.contains('${path.separator}packages'))).toList(); |
| 522 |
| 523 files.forEach((String f) { |
| 524 // Only include libraries at the top level of "lib" |
| 525 if (path.basename(path.dirname(f)) == 'lib') { |
| 526 // Only add the file if it does not contain 'part of' |
| 527 // TODO(janicejl): Remove when Issue(12406) is resolved. |
| 528 var contents = new File(f).readAsStringSync(); |
| 529 if (!(contents.contains(new RegExp('\npart of ')) || |
| 530 contents.startsWith(new RegExp('part of ')))) { |
| 531 libraries.add(new Uri.file(path.normalize(path.absolute(f)))); |
| 532 logger.info('Added to libraries: $f'); |
| 533 } |
| 534 } |
| 535 }); |
| 536 return libraries; |
| 537 } |
| 538 |
| 539 /// All of the directories for our dependent packages |
| 540 static List<String> _allDependentPackageDirs(String packageDirectory) { |
| 541 var dependentsJson = Process.runSync('pub', ['list-package-dirs'], |
| 542 workingDirectory: packageDirectory, runInShell: true); |
| 543 if (dependentsJson.exitCode != 0) { |
| 544 print(dependentsJson.stderr); |
| 545 } |
| 546 var dependents = JSON.decode(dependentsJson.stdout)['packages']; |
| 547 return dependents.values.toList(); |
| 548 } |
| 549 |
| 550 /// For all the libraries, return a list of the libraries that are part of |
| 551 /// the SDK. |
| 552 static List<Uri> _listSdk() { |
| 553 var sdk = new List<Uri>(); |
| 554 LIBRARIES.forEach((String name, LibraryInfo info) { |
| 555 if (info.documented) { |
| 556 sdk.add(Uri.parse('dart:$name')); |
| 557 logger.info('Add to SDK: ${sdk.last}'); |
| 558 } |
| 559 }); |
| 560 return sdk; |
| 561 } |
| 562 |
| 563 static bool _isFullChainVisible(MirrorBased item) { |
| 564 // TODO: reconcile with isVisible |
| 565 var result = _includePrivate || (!item.isPrivate && (item.owner != null ? |
| 566 _isFullChainVisible(item.owner) : true)); |
| 567 return result; |
| 568 } |
| 569 |
| 570 /// Currently left public for testing purposes. :-/ |
| 571 static Library generateLibrary(dart2js.Dart2JsLibraryMirror library) { |
| 572 var result = new Library(library); |
| 573 result._findPackage(library); |
| 574 logger.fine('Generated library for ${result.name}'); |
| 575 return result; |
| 576 } |
| 304 } | 577 } |
| 305 | 578 |
| 306 /// Analyzes set of libraries and provides a mirror system which can be used | 579 /// An item that is categorized in our mirrorToDocgen map, as a distinct, |
| 307 /// for static inspection of the source code. | 580 /// searchable element. |
| 308 Future<MirrorSystem> _analyzeLibraries(List<Uri> libraries, | |
| 309 String libraryRoot, {String packageRoot}) { | |
| 310 SourceFileProvider provider = new CompilerSourceFileProvider(); | |
| 311 api.DiagnosticHandler diagnosticHandler = | |
| 312 (new FormattingDiagnosticHandler(provider) | |
| 313 ..showHints = false | |
| 314 ..showWarnings = false) | |
| 315 .diagnosticHandler; | |
| 316 Uri libraryUri = new Uri.file(appendSlash(libraryRoot)); | |
| 317 Uri packageUri = null; | |
| 318 if (packageRoot != null) { | |
| 319 packageUri = new Uri.file(appendSlash(packageRoot)); | |
| 320 } | |
| 321 return dart2js.analyze(libraries, libraryUri, packageUri, | |
| 322 provider.readStringFromUri, diagnosticHandler, | |
| 323 ['--preserve-comments', '--categories=Client,Server']) | |
| 324 ..catchError((error) { | |
| 325 logger.severe('Error: Failed to create mirror system. '); | |
| 326 // TODO(janicejl): Use the stack trace package when bug is resolved. | |
| 327 // Currently, a string is thrown when it fails to create a mirror | |
| 328 // system, and it is not possible to use the stack trace. BUG(#11622) | |
| 329 // To avoid printing the stack trace. | |
| 330 exit(1); | |
| 331 }); | |
| 332 } | |
| 333 | |
| 334 /// Creates documentation for filtered libraries. | |
| 335 void _documentLibraries(List<LibraryMirror> libs, {bool includeSdk: false, | |
| 336 bool outputToYaml: true, bool append: false, bool parseSdk: false, | |
| 337 String introduction: ''}) { | |
| 338 libs.forEach((lib) { | |
| 339 // Files belonging to the SDK have a uri that begins with 'dart:'. | |
| 340 if (includeSdk || !lib.uri.toString().startsWith('dart:')) { | |
| 341 var library = generateLibrary(lib); | |
| 342 entityMap[library.name] = library; | |
| 343 } | |
| 344 }); | |
| 345 // After everything is created, do a pass through all classes to make sure no | |
| 346 // intermediate classes created by mixins are included, all the links to | |
| 347 // exported members point to the new library. | |
| 348 entityMap.values.where((e) => e is Class).forEach( | |
| 349 (c) => c.updateLinksAndRemoveIntermediaryClasses()); | |
| 350 // Everything is a subclass of Object, therefore empty the list to avoid a | |
| 351 // giant list of subclasses to be printed out. | |
| 352 if (includeSdk) (entityMap['dart-core.Object'] as Class).subclasses.clear(); | |
| 353 | |
| 354 var filteredEntities = entityMap.values.where(_isVisible); | |
| 355 | |
| 356 // Outputs a JSON file with all libraries and their preview comments. | |
| 357 // This will help the viewer know what libraries are available to read in. | |
| 358 var libraryMap; | |
| 359 var linkResolver = (name) => fixReference(name, null, null, null); | |
| 360 if (append) { | |
| 361 var docsDir = listDir(_outputDirectory); | |
| 362 if (!docsDir.contains('$_outputDirectory/library_list.json')) { | |
| 363 throw new StateError('No library_list.json'); | |
| 364 } | |
| 365 libraryMap = | |
| 366 JSON.decode(new File( | |
| 367 '$_outputDirectory/library_list.json').readAsStringSync()); | |
| 368 libraryMap['libraries'].addAll(filteredEntities | |
| 369 .where((e) => e is Library) | |
| 370 .map((e) => e.previewMap)); | |
| 371 if (introduction.isNotEmpty) { | |
| 372 var intro = libraryMap['introduction']; | |
| 373 if (intro.isNotEmpty) intro += '<br/><br/>'; | |
| 374 intro += markdown.markdownToHtml( | |
| 375 new File(introduction).readAsStringSync(), | |
| 376 linkResolver: linkResolver, inlineSyntaxes: markdownSyntaxes); | |
| 377 libraryMap['introduction'] = intro; | |
| 378 } | |
| 379 outputToYaml = libraryMap['filetype'] == 'yaml'; | |
| 380 } else { | |
| 381 libraryMap = { | |
| 382 'libraries' : filteredEntities.where((e) => | |
| 383 e is Library).map((e) => e.previewMap).toList(), | |
| 384 'introduction' : introduction == '' ? | |
| 385 '' : markdown.markdownToHtml(new File(introduction) | |
| 386 .readAsStringSync(), linkResolver: linkResolver, | |
| 387 inlineSyntaxes: markdownSyntaxes), | |
| 388 'filetype' : outputToYaml ? 'yaml' : 'json' | |
| 389 }; | |
| 390 } | |
| 391 _writeToFile(JSON.encode(libraryMap), 'library_list.json'); | |
| 392 | |
| 393 // Output libraries and classes to file after all information is generated. | |
| 394 filteredEntities.where((e) => e is Class || e is Library).forEach((output) { | |
| 395 _writeIndexableToFile(output, outputToYaml); | |
| 396 }); | |
| 397 | |
| 398 // Outputs all the qualified names documented with their type. | |
| 399 // This will help generate search results. | |
| 400 _writeToFile(filteredEntities.map((e) => | |
| 401 '${e.qualifiedName} ${e.typeName}').join('\n') + '\n', | |
| 402 'index.txt', append: append); | |
| 403 var index = new Map.fromIterables( | |
| 404 filteredEntities.map((e) => e.qualifiedName), | |
| 405 filteredEntities.map((e) => e.typeName)); | |
| 406 if (append) { | |
| 407 var previousIndex = | |
| 408 JSON.decode(new File('$_outputDirectory/index.json').readAsStringSync())
; | |
| 409 index.addAll(previousIndex); | |
| 410 } | |
| 411 _writeToFile(JSON.encode(index), 'index.json'); | |
| 412 } | |
| 413 | |
| 414 Library generateLibrary(dart2js.Dart2JsLibraryMirror library) { | |
| 415 var result = new Library(library); | |
| 416 _findPackage(library, result); | |
| 417 logger.fine('Generated library for ${result.name}'); | |
| 418 return result; | |
| 419 } | |
| 420 | |
| 421 void _writeIndexableToFile(Indexable result, bool outputToYaml) { | |
| 422 var outputFile = result.fileName; | |
| 423 var output; | |
| 424 if (outputToYaml) { | |
| 425 output = getYamlString(result.toMap()); | |
| 426 outputFile = outputFile + '.yaml'; | |
| 427 } else { | |
| 428 output = JSON.encode(result.toMap()); | |
| 429 outputFile = outputFile + '.json'; | |
| 430 } | |
| 431 _writeToFile(output, outputFile); | |
| 432 } | |
| 433 | |
| 434 /// Returns true if a library name starts with an underscore, and false | |
| 435 /// otherwise. | |
| 436 /// | 581 /// |
| 437 /// An example that starts with _ is _js_helper. | 582 /// These are items that refer to concrete entities (a Class, for example, |
| 438 /// An example that contains ._ is dart._collection.dev | 583 /// but not a Type, which is a "pointer" to a class) that we wish to be |
| 439 // This is because LibraryMirror.isPrivate returns `false` all the time. | 584 /// globally resolvable. This includes things such as class methods and |
| 440 bool _isLibraryPrivate(LibraryMirror mirror) { | 585 /// variables, but parameters for methods are not "Indexable" as we do not want |
| 441 var sdkLibrary = LIBRARIES[mirror.simpleName]; | 586 /// the user to be able to search for a method based on its parameter names! |
| 442 if (sdkLibrary != null) { | 587 /// The set of indexable items also includes Typedefs, since the user can refer |
| 443 return !sdkLibrary.documented; | 588 /// to them as concrete entities in a particular scope. |
| 444 } else if (mirror.simpleName.startsWith('_') || | 589 class Indexable extends MirrorBased { |
| 445 mirror.simpleName.contains('._')) { | 590 /// The dart:core library, which contains all types that are always available |
| 446 return true; | 591 /// without import. |
| 447 } | 592 static Library _coreLibrary; |
| 448 return false; | 593 |
| 449 } | 594 /// Set of libraries declared in the SDK, so libraries that can be accessed |
| 450 | 595 /// when running dart by default. |
| 451 /// A declaration is private if itself is private, or the owner is private. | 596 static Iterable<LibraryMirror> _sdkLibraries; |
| 452 // Issue(12202) - A declaration is public even if it's owner is private. | 597 |
| 453 bool _isHidden(DeclarationMirror mirror) { | |
| 454 if (mirror is LibraryMirror) { | |
| 455 return _isLibraryPrivate(mirror); | |
| 456 } else if (mirror.owner is LibraryMirror) { | |
| 457 return (mirror.isPrivate || _isLibraryPrivate(mirror.owner)); | |
| 458 } else { | |
| 459 return (mirror.isPrivate || _isHidden(mirror.owner)); | |
| 460 } | |
| 461 } | |
| 462 | |
| 463 bool _isVisible(Indexable item) { | |
| 464 return _includePrivate || !item.isPrivate; | |
| 465 } | |
| 466 | |
| 467 /// Generates MDN comments from database.json. | |
| 468 void _mdnComment(Indexable item) { | |
| 469 //Check if MDN is loaded. | |
| 470 if (_mdn == null) { | |
| 471 // Reading in MDN related json file. | |
| 472 var root = findRootDirectory(); | |
| 473 var mdnPath = path.join(root, 'utils/apidoc/mdn/database.json'); | |
| 474 _mdn = JSON.decode(new File(mdnPath).readAsStringSync()); | |
| 475 } | |
| 476 if (item is Library) return; | |
| 477 var domAnnotation = item.annotations.firstWhere( | |
| 478 (e) => e.qualifiedName == 'metadata.DomName', orElse: () => null); | |
| 479 if (domAnnotation == null) return; | |
| 480 var domName = domAnnotation.parameters.single; | |
| 481 var parts = domName.split('.'); | |
| 482 if (parts.length == 2) item.comment = _mdnMemberComment(parts[0], parts[1]); | |
| 483 if (parts.length == 1) item.comment = _mdnTypeComment(parts[0]); | |
| 484 } | |
| 485 | |
| 486 /// Generates the MDN Comment for variables and method DOM elements. | |
| 487 String _mdnMemberComment(String type, String member) { | |
| 488 var mdnType = _mdn[type]; | |
| 489 if (mdnType == null) return ''; | |
| 490 var mdnMember = mdnType['members'].firstWhere((e) => e['name'] == member, | |
| 491 orElse: () => null); | |
| 492 if (mdnMember == null) return ''; | |
| 493 if (mdnMember['help'] == null || mdnMember['help'] == '') return ''; | |
| 494 if (mdnMember['url'] == null) return ''; | |
| 495 return _htmlMdn(mdnMember['help'], mdnMember['url']); | |
| 496 } | |
| 497 | |
| 498 /// Generates the MDN Comment for class DOM elements. | |
| 499 String _mdnTypeComment(String type) { | |
| 500 var mdnType = _mdn[type]; | |
| 501 if (mdnType == null) return ''; | |
| 502 if (mdnType['summary'] == null || mdnType['summary'] == "") return ''; | |
| 503 if (mdnType['srcUrl'] == null) return ''; | |
| 504 return _htmlMdn(mdnType['summary'], mdnType['srcUrl']); | |
| 505 } | |
| 506 | |
| 507 String _htmlMdn(String content, String url) { | |
| 508 return '<div class="mdn">' + content.trim() + '<p class="mdn-note">' | |
| 509 '<a href="' + url.trim() + '">from Mdn</a></p></div>'; | |
| 510 } | |
| 511 | |
| 512 /// Look for the specified name starting with the current member, and | |
| 513 /// progressively working outward to the current library scope. | |
| 514 String findElementInScope(String name, LibraryMirror currentLibrary, | |
| 515 ClassMirror currentClass, MemberMirror currentMember) { | |
| 516 var packagePrefix = _findPackage(currentLibrary); | |
| 517 if (packagePrefix != '') packagePrefix += '/'; | |
| 518 | |
| 519 determineLookupFunc(name) => name.contains('.') ? | |
| 520 dart2js_util.lookupQualifiedInScope : | |
| 521 (mirror, name) => mirror.lookupInScope(name); | |
| 522 var lookupFunc = determineLookupFunc(name); | |
| 523 | |
| 524 var memberScope = currentMember == null ? | |
| 525 null : lookupFunc(currentMember, name); | |
| 526 if (memberScope != null) return packagePrefix + docName(memberScope); | |
| 527 | |
| 528 var classScope = currentClass; | |
| 529 while (classScope != null) { | |
| 530 var classFunc = lookupFunc(currentClass, name); | |
| 531 if (classFunc != null) return packagePrefix + docName(classFunc); | |
| 532 classScope = classScope.superclass; | |
| 533 } | |
| 534 | |
| 535 var libraryScope = currentLibrary == null ? | |
| 536 null : lookupFunc(currentLibrary, name); | |
| 537 if (libraryScope != null) return packagePrefix + docName(libraryScope); | |
| 538 | |
| 539 // Look in the dart core library scope. | |
| 540 var coreScope = _coreLibrary == null? null : lookupFunc(_coreLibrary, name); | |
| 541 if (coreScope != null) return packagePrefix + docName(_coreLibrary); | |
| 542 | |
| 543 // If it's a reference that starts with a another library name, then it | |
| 544 // looks for a match of that library name in the other sdk libraries. | |
| 545 if(name.contains('.')) { | |
| 546 var index = name.indexOf('.'); | |
| 547 var libraryName = name.substring(0, index); | |
| 548 var remainingName = name.substring(index + 1); | |
| 549 foundLibraryName(library) => library.uri.pathSegments[0] == libraryName; | |
| 550 | |
| 551 if (_sdkLibraries.any(foundLibraryName)) { | |
| 552 var library = _sdkLibraries.singleWhere(foundLibraryName); | |
| 553 // Look to see if it's a fully qualified library name. | |
| 554 var scope = determineLookupFunc(remainingName)(library, remainingName); | |
| 555 if (scope != null) return packagePrefix + docName(scope); | |
| 556 } | |
| 557 } | |
| 558 return null; | |
| 559 } | |
| 560 | |
| 561 // HTML escaped version of '<' character. | |
| 562 final _LESS_THAN = '<'; | |
| 563 | |
| 564 /// Chunk the provided name into individual parts to be resolved. We take a | |
| 565 /// simplistic approach to chunking, though, we break at " ", ",", "<" | |
| 566 /// and ">". All other characters are grouped into the name to be resolved. | |
| 567 /// As a result, these characters will all be treated as part of the item to be | |
| 568 /// resolved (aka the * is interpreted literally as a *, not as an indicator for | |
| 569 /// bold <em>. | |
| 570 List<String> _tokenizeComplexReference(String name) { | |
| 571 var tokens = []; | |
| 572 var append = false; | |
| 573 var index = 0; | |
| 574 while(index < name.length) { | |
| 575 if (name.indexOf(_LESS_THAN, index) == index) { | |
| 576 tokens.add(_LESS_THAN); | |
| 577 append = false; | |
| 578 index += _LESS_THAN.length; | |
| 579 } else if (name[index] == ' ' || name[index] == ',' || | |
| 580 name[index] == '>') { | |
| 581 tokens.add(name[index]); | |
| 582 append = false; | |
| 583 index++; | |
| 584 } else { | |
| 585 if (append) { | |
| 586 tokens[tokens.length - 1] = tokens.last + name[index]; | |
| 587 } else { | |
| 588 tokens.add(name[index]); | |
| 589 append = true; | |
| 590 } | |
| 591 index++; | |
| 592 } | |
| 593 } | |
| 594 return tokens; | |
| 595 } | |
| 596 | |
| 597 /// This is a more complex reference. Try to break up if its of the form A<B> | |
| 598 /// where A is an alphanumeric string and B is an A, a list of B ("B, B, B"), | |
| 599 /// or of the form A<B>. Note: unlike other the other markdown-style links, all | |
| 600 /// text inside the square brackets is treated as part of the link (aka the * is | |
| 601 /// interpreted literally as a *, not as a indicator for bold <em>. | |
| 602 /// | |
| 603 /// Example: [foo<_bar_>] will produce | |
| 604 /// <a>resolvedFoo</a><<a>resolved_bar_</a>> rather than an italicized | |
| 605 /// version of resolvedBar. | |
| 606 markdown.Node _fixComplexReference(String name, LibraryMirror currentLibrary, | |
| 607 ClassMirror currentClass, MemberMirror currentMember) { | |
| 608 // Parse into multiple elements we can try to resolve. | |
| 609 var tokens = _tokenizeComplexReference(name); | |
| 610 | |
| 611 // Produce an html representation of our elements. Group unresolved and plain | |
| 612 // text are grouped into "link" elements so they display as code. | |
| 613 final textElements = [' ', ',', '>', _LESS_THAN]; | |
| 614 var accumulatedHtml = ''; | |
| 615 | |
| 616 for (var token in tokens) { | |
| 617 bool added = false; | |
| 618 if (!textElements.contains(token)) { | |
| 619 String elementName = findElementInScope(token, currentLibrary, | |
| 620 currentClass, currentMember); | |
| 621 if (elementName != null) { | |
| 622 accumulatedHtml += markdown.renderToHtml([new markdown.Element.text( | |
| 623 'a', elementName)]); | |
| 624 added = true; | |
| 625 } | |
| 626 } | |
| 627 if (!added) { | |
| 628 accumulatedHtml += token; | |
| 629 } | |
| 630 } | |
| 631 return new markdown.Text(accumulatedHtml); | |
| 632 } | |
| 633 | |
| 634 /// Converts all [foo] references in comments to <a>libraryName.foo</a>. | |
| 635 markdown.Node fixReference(String name, LibraryMirror currentLibrary, | |
| 636 ClassMirror currentClass, MemberMirror currentMember) { | |
| 637 // Attempt the look up the whole name up in the scope. | |
| 638 String elementName = | |
| 639 findElementInScope(name, currentLibrary, currentClass, currentMember); | |
| 640 if (elementName != null) { | |
| 641 return new markdown.Element.text('a', elementName); | |
| 642 } | |
| 643 return _fixComplexReference(name, currentLibrary, currentClass, | |
| 644 currentMember); | |
| 645 } | |
| 646 | |
| 647 markdown.Node fixReferenceWithScope(String name, DeclarationMirror scope) { | |
| 648 if (scope is LibraryMirror) return fixReference(name, scope, null, null); | |
| 649 if (scope is ClassMirror) | |
| 650 return fixReference(name, scope.library, scope, null); | |
| 651 if (scope is MemberMirror) { | |
| 652 var owner = scope.owner; | |
| 653 if (owner is ClassMirror) { | |
| 654 return fixReference(name, owner.library, owner, scope); | |
| 655 } else { | |
| 656 return fixReference(name, owner, null, scope); | |
| 657 } | |
| 658 } | |
| 659 return null; | |
| 660 } | |
| 661 | |
| 662 /// Writes text to a file in the output directory. | |
| 663 void _writeToFile(String text, String filename, {bool append: false}) { | |
| 664 if (text == null) return; | |
| 665 Directory dir = new Directory(_outputDirectory); | |
| 666 if (!dir.existsSync()) { | |
| 667 dir.createSync(); | |
| 668 } | |
| 669 if (path.split(filename).length > 1) { | |
| 670 var splitList = path.split(filename); | |
| 671 for (int i = 0; i < splitList.length; i++) { | |
| 672 var level = splitList[i]; | |
| 673 } | |
| 674 for (var level in path.split(filename)) { | |
| 675 var subdir = new Directory(path.join(_outputDirectory, | |
| 676 path.dirname(filename))); | |
| 677 if (!subdir.existsSync()) { | |
| 678 subdir.createSync(); | |
| 679 } | |
| 680 } | |
| 681 } | |
| 682 File file = new File(path.join(_outputDirectory, filename)); | |
| 683 file.writeAsStringSync(text, mode: append ? FileMode.APPEND : FileMode.WRITE); | |
| 684 } | |
| 685 | |
| 686 /// Transforms the map by calling toMap on each value in it. | |
| 687 Map recurseMap(Map inputMap) { | |
| 688 var outputMap = {}; | |
| 689 inputMap.forEach((key, value) { | |
| 690 if (value is Map) { | |
| 691 outputMap[key] = recurseMap(value); | |
| 692 } else { | |
| 693 outputMap[key] = value.toMap(); | |
| 694 } | |
| 695 }); | |
| 696 return outputMap; | |
| 697 } | |
| 698 | |
| 699 /// A type for the function that generates a comment from a mirror. | |
| 700 typedef String CommentGenerator(Mirror m); | |
| 701 | |
| 702 /// A class representing all programming constructs, like library or class. | |
| 703 class Indexable { | |
| 704 String get qualifiedName => fileName; | 598 String get qualifiedName => fileName; |
| 705 bool isPrivate; | 599 bool isPrivate; |
| 706 DeclarationMirror mirror; | 600 DeclarationMirror mirror; |
| 601 /// The comment text pre-resolution. We keep this around because inherited |
| 602 /// methods need to resolve links differently from the superclass. |
| 603 String _unresolvedComment = ''; |
| 604 |
| 605 // TODO(janicejl): Make MDN content generic or pluggable. Maybe move |
| 606 // MDN-specific code to its own library that is imported into the default |
| 607 // impl? |
| 608 /// Map of all the comments for dom elements from MDN. |
| 609 static Map _mdn; |
| 707 | 610 |
| 708 Indexable(this.mirror) { | 611 Indexable(this.mirror) { |
| 709 this.isPrivate = _isHidden(mirror); | 612 this.isPrivate = _isHidden(mirror); |
| 710 } | 613 |
| 614 var map = mirrorToDocgen[this.mirror.qualifiedName]; |
| 615 if (map == null) map = new Map<String, Set<MirrorBased>>(); |
| 616 |
| 617 var set = map[owner.docName]; |
| 618 if (set == null) set = new Set<MirrorBased>(); |
| 619 set.add(this); |
| 620 map[owner.docName] = set; |
| 621 mirrorToDocgen[this.mirror.qualifiedName] = map; |
| 622 } |
| 623 |
| 624 /** Walk up the owner chain to find the owning library. */ |
| 625 Library _getOwningLibrary(Indexable owner) { |
| 626 if (owner is Library) return owner; |
| 627 // TODO: is this needed? |
| 628 if (owner is DummyMirror) return getDocgenObject(owner.mirror.library); |
| 629 return _getOwningLibrary(owner.owner); |
| 630 } |
| 631 |
| 632 static initializeTopLevelLibraries(MirrorSystem mirrorSystem) { |
| 633 _sdkLibraries = mirrorSystem.libraries.values.where( |
| 634 (each) => each.uri.scheme == 'dart'); |
| 635 _coreLibrary = new Library(_sdkLibraries.singleWhere((lib) => |
| 636 lib.uri.toString().startsWith('dart:core'))); |
| 637 } |
| 638 |
| 639 markdown.Node fixReferenceWithScope(String name) => null; |
| 640 |
| 641 /// Converts all [foo] references in comments to <a>libraryName.foo</a>. |
| 642 markdown.Node fixReference(String name) { |
| 643 // Attempt the look up the whole name up in the scope. |
| 644 String elementName = findElementInScope(name); |
| 645 if (elementName != null) { |
| 646 return new markdown.Element.text('a', elementName); |
| 647 } |
| 648 return _fixComplexReference(name); |
| 649 } |
| 650 |
| 651 /// Look for the specified name starting with the current member, and |
| 652 /// progressively working outward to the current library scope. |
| 653 String findElementInScope(String name) => |
| 654 _findElementInScope(name, packagePrefix); |
| 655 |
| 656 static determineLookupFunc(name) => name.contains('.') ? |
| 657 dart2js_util.lookupQualifiedInScope : |
| 658 (mirror, name) => mirror.lookupInScope(name); |
| 711 | 659 |
| 712 // The qualified name (for URL purposes) and the file name are the same, | 660 // The qualified name (for URL purposes) and the file name are the same, |
| 713 // of the form packageName/ClassName or packageName/ClassName.methodName. | 661 // of the form packageName/ClassName or packageName/ClassName.methodName. |
| 714 // This defines both the URL and the directory structure. | 662 // This defines both the URL and the directory structure. |
| 715 String get fileName => packagePrefix + ownerPrefix + name; | 663 String get fileName { |
| 716 | 664 return packagePrefix + ownerPrefix + name; |
| 717 Indexable get owningEntity => entityMap[owner]; | 665 } |
| 718 | 666 |
| 719 String get ownerPrefix => owningEntity == null ? | 667 String get ownerPrefix => owner.docName != '' ? owner.docName + '.' : ''; |
| 720 (owner == null || owner.isEmpty ? '' : owner + '.') : | |
| 721 owningEntity.qualifiedName + '.'; | |
| 722 | 668 |
| 723 String get packagePrefix => ''; | 669 String get packagePrefix => ''; |
| 724 | 670 |
| 725 /// Documentation comment with converted markdown. | 671 /// Documentation comment with converted markdown. |
| 726 String _comment; | 672 String _comment; |
| 727 | 673 |
| 728 String get comment { | 674 String get comment { |
| 729 if (_comment != null) return _comment; | 675 if (_comment != null) return _comment; |
| 730 _comment = _commentToHtml(mirror); | 676 |
| 677 _comment = _commentToHtml(); |
| 731 if (_comment.isEmpty) { | 678 if (_comment.isEmpty) { |
| 732 _mdnComment(this); | 679 _comment = _mdnComment(); |
| 733 } | 680 } |
| 734 return _comment; | 681 return _comment; |
| 735 } | 682 } |
| 736 | 683 |
| 737 set comment(x) => _comment = x; | 684 set comment(x) => _comment = x; |
| 738 | 685 |
| 739 String get name => mirror.simpleName; | 686 String get name => mirror.simpleName; |
| 740 | 687 |
| 741 /// Qualified Name of the owner of this Indexable Item. | 688 MirrorBased get owner => new DummyMirror(mirror.owner); |
| 742 String get owner => docName(mirror.owner); | 689 |
| 690 /// Generates MDN comments from database.json. |
| 691 String _mdnComment() { |
| 692 //Check if MDN is loaded. |
| 693 if (_mdn == null) { |
| 694 // Reading in MDN related json file. |
| 695 var root = _Generator._rootDirectory; |
| 696 var mdnPath = path.join(root, 'utils/apidoc/mdn/database.json'); |
| 697 _mdn = JSON.decode(new File(mdnPath).readAsStringSync()); |
| 698 } |
| 699 // TODO: refactor OOP |
| 700 if (this is Library) return ''; |
| 701 var domAnnotation = this.annotations.firstWhere( |
| 702 (e) => e.mirror.qualifiedName == 'metadata.DomName', |
| 703 orElse: () => null); |
| 704 if (domAnnotation == null) return ''; |
| 705 var domName = domAnnotation.parameters.single; |
| 706 var parts = domName.split('.'); |
| 707 if (parts.length == 2) return _mdnMemberComment(parts[0], parts[1]); |
| 708 if (parts.length == 1) return _mdnTypeComment(parts[0]); |
| 709 } |
| 710 |
| 711 /// Generates the MDN Comment for variables and method DOM elements. |
| 712 String _mdnMemberComment(String type, String member) { |
| 713 var mdnType = _mdn[type]; |
| 714 if (mdnType == null) return ''; |
| 715 var mdnMember = mdnType['members'].firstWhere((e) => e['name'] == member, |
| 716 orElse: () => null); |
| 717 if (mdnMember == null) return ''; |
| 718 if (mdnMember['help'] == null || mdnMember['help'] == '') return ''; |
| 719 if (mdnMember['url'] == null) return ''; |
| 720 return _htmlMdn(mdnMember['help'], mdnMember['url']); |
| 721 } |
| 722 |
| 723 /// Generates the MDN Comment for class DOM elements. |
| 724 String _mdnTypeComment(String type) { |
| 725 var mdnType = _mdn[type]; |
| 726 if (mdnType == null) return ''; |
| 727 if (mdnType['summary'] == null || mdnType['summary'] == "") return ''; |
| 728 if (mdnType['srcUrl'] == null) return ''; |
| 729 return _htmlMdn(mdnType['summary'], mdnType['srcUrl']); |
| 730 } |
| 731 |
| 732 String _htmlMdn(String content, String url) { |
| 733 return '<div class="mdn">' + content.trim() + '<p class="mdn-note">' |
| 734 '<a href="' + url.trim() + '">from Mdn</a></p></div>'; |
| 735 } |
| 743 | 736 |
| 744 /// The type of this member to be used in index.txt. | 737 /// The type of this member to be used in index.txt. |
| 745 String get typeName => ''; | 738 String get typeName => ''; |
| 746 | 739 |
| 747 /// Creates a [Map] with this [Indexable]'s name and a preview comment. | 740 /// Creates a [Map] with this [Indexable]'s name and a preview comment. |
| 748 Map get previewMap { | 741 Map get previewMap { |
| 749 var finalMap = { 'name' : name, 'qualifiedName' : qualifiedName }; | 742 var finalMap = { 'name' : name, 'qualifiedName' : qualifiedName }; |
| 750 if (comment != '') { | 743 if (comment != '') { |
| 751 var index = comment.indexOf('</p>'); | 744 var index = comment.indexOf('</p>'); |
| 752 finalMap['preview'] = '${comment.substring(0, index)}</p>'; | 745 finalMap['preview'] = '${comment.substring(0, index)}</p>'; |
| 753 } | 746 } |
| 754 return finalMap; | 747 return finalMap; |
| 755 } | 748 } |
| 756 | 749 |
| 757 /// Returns any documentation comments associated with a mirror with | 750 String _getCommentText() { |
| 758 /// simple markdown converted to html. | |
| 759 /// | |
| 760 /// It's possible to have a comment that comes from one mirror applied to | |
| 761 /// another, in the case of an inherited comment. | |
| 762 String _commentToHtml(itemToDocument) { | |
| 763 String commentText; | 751 String commentText; |
| 764 mirror.metadata.forEach((metadata) { | 752 mirror.metadata.forEach((metadata) { |
| 765 if (metadata is CommentInstanceMirror) { | 753 if (metadata is CommentInstanceMirror) { |
| 766 CommentInstanceMirror comment = metadata; | 754 CommentInstanceMirror comment = metadata; |
| 767 if (comment.isDocComment) { | 755 if (comment.isDocComment) { |
| 768 if (commentText == null) { | 756 if (commentText == null) { |
| 769 commentText = comment.trimmedText; | 757 commentText = comment.trimmedText; |
| 770 } else { | 758 } else { |
| 771 commentText = '$commentText\n${comment.trimmedText}'; | 759 commentText = '$commentText\n${comment.trimmedText}'; |
| 772 } | 760 } |
| 773 } | 761 } |
| 774 } | 762 } |
| 775 }); | 763 }); |
| 776 | |
| 777 var linkResolver = (name) => fixReferenceWithScope(name, itemToDocument); | |
| 778 commentText = commentText == null ? '' : | |
| 779 markdown.markdownToHtml(commentText.trim(), linkResolver: linkResolver, | |
| 780 inlineSyntaxes: markdownSyntaxes); | |
| 781 return commentText; | 764 return commentText; |
| 782 } | 765 } |
| 783 | 766 |
| 767 /// Returns any documentation comments associated with a mirror with |
| 768 /// simple markdown converted to html. |
| 769 /// |
| 770 /// By default we resolve any comment references within our own scope. |
| 771 /// However, if a method is inherited, we want the inherited comments, but |
| 772 /// links to the subclasses's version of the methods. |
| 773 String _commentToHtml([Indexable resolvingScope]) { |
| 774 if (resolvingScope == null) resolvingScope = this; |
| 775 var commentText = _getCommentText(); |
| 776 _unresolvedComment = commentText; |
| 777 |
| 778 var linkResolver = (name) => resolvingScope.fixReferenceWithScope(name); |
| 779 commentText = commentText == null ? '' : |
| 780 markdown.markdownToHtml(commentText.trim(), linkResolver: linkResolver, |
| 781 inlineSyntaxes: _MARKDOWN_SYNTAXES); |
| 782 return commentText; |
| 783 } |
| 784 |
| 784 /// Returns a map of [Variable] objects constructed from [mirrorMap]. | 785 /// Returns a map of [Variable] objects constructed from [mirrorMap]. |
| 785 /// The optional parameter [containingLibrary] is contains data for variables | 786 /// The optional parameter [containingLibrary] is contains data for variables |
| 786 /// defined at the top level of a library (potentially for exporting | 787 /// defined at the top level of a library (potentially for exporting |
| 787 /// purposes). | 788 /// purposes). |
| 788 Map<String, Variable> _createVariables(Map<String, | 789 Map<String, Variable> _createVariables(Map<String, VariableMirror> mirrorMap, |
| 789 VariableMirror> mirrorMap, [Library containingLibrary]) { | 790 Indexable owner) { |
| 790 var data = {}; | 791 var data = {}; |
| 791 // TODO(janicejl): When map to map feature is created, replace the below | 792 // TODO(janicejl): When map to map feature is created, replace the below |
| 792 // with a filter. Issue(#9590). | 793 // with a filter. Issue(#9590). |
| 793 mirrorMap.forEach((String mirrorName, VariableMirror mirror) { | 794 mirrorMap.forEach((String mirrorName, VariableMirror mirror) { |
| 794 if (_includePrivate || !_isHidden(mirror)) { | 795 if (_Generator._includePrivate || !_isHidden(mirror)) { |
| 795 if (containingLibrary != null && mirror.owner.qualifiedName != | 796 var variable = new Variable(mirrorName, mirror, owner); |
| 796 containingLibrary.mirror.qualifiedName) { | 797 entityMap[variable.docName] = variable; |
| 797 entityMap[docName(mirror)] = new ExportedVariable(mirrorName, mirror, | 798 data[mirrorName] = entityMap[variable.docName]; |
| 798 containingLibrary); | |
| 799 } else { | |
| 800 entityMap[docName(mirror)] = new Variable(mirrorName, mirror); | |
| 801 } | |
| 802 data[mirrorName] = entityMap[docName(mirror)]; | |
| 803 } | 799 } |
| 804 }); | 800 }); |
| 805 return data; | 801 return data; |
| 806 } | 802 } |
| 807 | 803 |
| 808 /// Returns a map of [Method] objects constructed from [mirrorMap]. | 804 /// Returns a map of [Method] objects constructed from [mirrorMap]. |
| 809 /// The optional parameter [containingLibrary] is contains data for variables | 805 /// The optional parameter [containingLibrary] is contains data for variables |
| 810 /// defined at the top level of a library (potentially for exporting | 806 /// defined at the top level of a library (potentially for exporting |
| 811 /// purposes). | 807 /// purposes). |
| 812 MethodGroup _createMethods(Map<String, MethodMirror> mirrorMap, | 808 Map<String, Method> _createMethods(Map<String, MethodMirror> mirrorMap, |
| 813 [Library containingLibrary]) { | 809 Indexable owner) { |
| 814 var group = new MethodGroup(); | 810 var group = new Map<String, Method>(); |
| 815 mirrorMap.forEach((String mirrorName, MethodMirror mirror) { | 811 mirrorMap.forEach((String mirrorName, MethodMirror mirror) { |
| 816 if (_includePrivate || !mirror.isPrivate) { | 812 if (_Generator._includePrivate || !mirror.isPrivate) { |
| 817 group.addMethod(mirror, containingLibrary); | 813 var method = new Method(mirror, owner); |
| 814 entityMap[method.docName] = method; |
| 815 group[mirror.simpleName] = method; |
| 818 } | 816 } |
| 819 }); | 817 }); |
| 820 return group; | 818 return group; |
| 821 } | 819 } |
| 822 | 820 |
| 823 /// Returns a map of [Parameter] objects constructed from [mirrorList]. | 821 /// Returns a map of [Parameter] objects constructed from [mirrorList]. |
| 824 Map<String, Parameter> _createParameters(List<ParameterMirror> mirrorList) { | 822 Map<String, Parameter> _createParameters(List<ParameterMirror> mirrorList, |
| 823 [Indexable owner]) { |
| 825 var data = {}; | 824 var data = {}; |
| 826 mirrorList.forEach((ParameterMirror mirror) { | 825 mirrorList.forEach((ParameterMirror mirror) { |
| 827 data[mirror.simpleName] = new Parameter(mirror.simpleName, | 826 data[mirror.simpleName] = new Parameter(mirror, owner); |
| 828 mirror.isOptional, mirror.isNamed, mirror.hasDefaultValue, | |
| 829 _createType(mirror.type), mirror.defaultValue, | |
| 830 _createAnnotations(mirror)); | |
| 831 }); | 827 }); |
| 832 return data; | 828 return data; |
| 833 } | 829 } |
| 834 | 830 |
| 835 /// Returns a map of [Generic] objects constructed from the class mirror. | 831 /// Returns a map of [Generic] objects constructed from the class mirror. |
| 836 Map<String, Generic> _createGenerics(ClassMirror mirror) { | 832 Map<String, Generic> _createGenerics(ClassMirror mirror) { |
| 837 return new Map.fromIterable(mirror.typeVariables, | 833 return new Map.fromIterable(mirror.typeVariables, |
| 838 key: (e) => e.toString(), | 834 key: (e) => e.toString(), |
| 839 value: (e) => new Generic(e.toString(), e.upperBound.qualifiedName)); | 835 value: (e) => new Generic(e)); |
| 840 } | |
| 841 | |
| 842 /// Returns a single [Type] object constructed from the Method.returnType | |
| 843 /// Type mirror. | |
| 844 Type _createType(TypeMirror mirror) { | |
| 845 return new Type(docName(mirror), _createTypeGenerics(mirror)); | |
| 846 } | |
| 847 | |
| 848 /// Returns a list of [Type] objects constructed from TypeMirrors. | |
| 849 List<Type> _createTypeGenerics(TypeMirror mirror) { | |
| 850 if (mirror is ClassMirror && !mirror.isTypedef) { | |
| 851 var innerList = []; | |
| 852 mirror.typeArguments.forEach((e) { | |
| 853 innerList.add(new Type(docName(e), _createTypeGenerics(e))); | |
| 854 }); | |
| 855 return innerList; | |
| 856 } | |
| 857 return []; | |
| 858 } | |
| 859 | |
| 860 /// Returns a list of meta annotations assocated with a mirror. | |
| 861 List<Annotation> _createAnnotations(DeclarationMirror mirror) { | |
| 862 var annotationMirrors = mirror.metadata.where((e) => | |
| 863 e is dart2js.Dart2JsConstructedConstantMirror); | |
| 864 var annotations = []; | |
| 865 annotationMirrors.forEach((annotation) { | |
| 866 var parameterList = annotation.type.variables.values | |
| 867 .where((e) => e.isFinal) | |
| 868 .map((e) => annotation.getField(e.simpleName).reflectee) | |
| 869 .where((e) => e != null) | |
| 870 .toList(); | |
| 871 if (!skippedAnnotations.contains(docName(annotation.type))) { | |
| 872 annotations.add(new Annotation(docName(annotation.type), | |
| 873 parameterList)); | |
| 874 } | |
| 875 }); | |
| 876 return annotations; | |
| 877 } | 836 } |
| 878 | 837 |
| 879 /// Return an informative [Object.toString] for debugging. | 838 /// Return an informative [Object.toString] for debugging. |
| 880 String toString() => "${super.toString()}(${name.toString()})"; | 839 String toString() => "${super.toString()}(${name.toString()})"; |
| 881 | 840 |
| 882 /// Return a map representation of this type. | 841 /// Return a map representation of this type. |
| 883 Map toMap() {} | 842 Map toMap() {} |
| 843 |
| 844 |
| 845 /// A declaration is private if itself is private, or the owner is private. |
| 846 // Issue(12202) - A declaration is public even if it's owner is private. |
| 847 bool _isHidden(DeclarationMirror mirror) { |
| 848 if (mirror is LibraryMirror) { |
| 849 return _isLibraryPrivate(mirror); |
| 850 } else if (mirror.owner is LibraryMirror) { |
| 851 return (mirror.isPrivate || _isLibraryPrivate(mirror.owner) |
| 852 || mirror.isNameSynthetic); |
| 853 } else { |
| 854 return (mirror.isPrivate || _isHidden(mirror.owner) |
| 855 || owner.mirror.isNameSynthetic); |
| 856 } |
| 857 } |
| 858 |
| 859 /// Returns true if a library name starts with an underscore, and false |
| 860 /// otherwise. |
| 861 /// |
| 862 /// An example that starts with _ is _js_helper. |
| 863 /// An example that contains ._ is dart._collection.dev |
| 864 // This is because LibraryMirror.isPrivate returns `false` all the time. |
| 865 bool _isLibraryPrivate(LibraryMirror mirror) { |
| 866 var sdkLibrary = LIBRARIES[mirror.simpleName]; |
| 867 if (sdkLibrary != null) { |
| 868 return !sdkLibrary.documented; |
| 869 } else if (mirror.simpleName.startsWith('_') || |
| 870 mirror.simpleName.contains('._')) { |
| 871 return true; |
| 872 } |
| 873 return false; |
| 874 } |
| 875 |
| 876 ////// Top level resolution functions |
| 877 /// Converts all [foo] references in comments to <a>libraryName.foo</a>. |
| 878 static markdown.Node globalFixReference(String name) { |
| 879 // Attempt the look up the whole name up in the scope. |
| 880 String elementName = _findElementInScope(name, ''); |
| 881 if (elementName != null) { |
| 882 return new markdown.Element.text('a', elementName); |
| 883 } |
| 884 return _fixComplexReference(name); |
| 885 } |
| 886 |
| 887 /// This is a more complex reference. Try to break up if its of the form A<B> |
| 888 /// where A is an alphanumeric string and B is an A, a list of B ("B, B, B"), |
| 889 /// or of the form A<B>. Note: unlike other the other markdown-style links, |
| 890 /// all text inside the square brackets is treated as part of the link (aka |
| 891 /// the * is interpreted literally as a *, not as a indicator for bold <em>. |
| 892 /// |
| 893 /// Example: [foo<_bar_>] will produce |
| 894 /// <a>resolvedFoo</a><<a>resolved_bar_</a>> rather than an italicized |
| 895 /// version of resolvedBar. |
| 896 static markdown.Node _fixComplexReference(String name) { |
| 897 // Parse into multiple elements we can try to resolve. |
| 898 var tokens = _tokenizeComplexReference(name); |
| 899 |
| 900 // Produce an html representation of our elements. Group unresolved and |
| 901 // plain text are grouped into "link" elements so they display as code. |
| 902 final textElements = [' ', ',', '>', _LESS_THAN]; |
| 903 var accumulatedHtml = ''; |
| 904 |
| 905 for (var token in tokens) { |
| 906 bool added = false; |
| 907 if (!textElements.contains(token)) { |
| 908 String elementName = _findElementInScope(token, ''); |
| 909 if (elementName != null) { |
| 910 accumulatedHtml += markdown.renderToHtml([new markdown.Element.text( |
| 911 'a', elementName)]); |
| 912 added = true; |
| 913 } |
| 914 } |
| 915 if (!added) { |
| 916 accumulatedHtml += token; |
| 917 } |
| 918 } |
| 919 return new markdown.Text(accumulatedHtml); |
| 920 } |
| 921 |
| 922 |
| 923 // HTML escaped version of '<' character. |
| 924 static final _LESS_THAN = '<'; |
| 925 |
| 926 /// Chunk the provided name into individual parts to be resolved. We take a |
| 927 /// simplistic approach to chunking, though, we break at " ", ",", "<" |
| 928 /// and ">". All other characters are grouped into the name to be resolved. |
| 929 /// As a result, these characters will all be treated as part of the item to |
| 930 /// be resolved (aka the * is interpreted literally as a *, not as an |
| 931 /// indicator for bold <em>. |
| 932 static List<String> _tokenizeComplexReference(String name) { |
| 933 var tokens = []; |
| 934 var append = false; |
| 935 var index = 0; |
| 936 while(index < name.length) { |
| 937 if (name.indexOf(_LESS_THAN, index) == index) { |
| 938 tokens.add(_LESS_THAN); |
| 939 append = false; |
| 940 index += _LESS_THAN.length; |
| 941 } else if (name[index] == ' ' || name[index] == ',' || |
| 942 name[index] == '>') { |
| 943 tokens.add(name[index]); |
| 944 append = false; |
| 945 index++; |
| 946 } else { |
| 947 if (append) { |
| 948 tokens[tokens.length - 1] = tokens.last + name[index]; |
| 949 } else { |
| 950 tokens.add(name[index]); |
| 951 append = true; |
| 952 } |
| 953 index++; |
| 954 } |
| 955 } |
| 956 return tokens; |
| 957 } |
| 958 |
| 959 static String _findElementInScope(String name, String packagePrefix) { |
| 960 var lookupFunc = determineLookupFunc(name); |
| 961 // Look in the dart core library scope. |
| 962 var coreScope = _coreLibrary == null? null : |
| 963 lookupFunc(_coreLibrary.mirror, name); |
| 964 if (coreScope != null) return packagePrefix + _coreLibrary.docName; |
| 965 |
| 966 // If it's a reference that starts with a another library name, then it |
| 967 // looks for a match of that library name in the other sdk libraries. |
| 968 if(name.contains('.')) { |
| 969 var index = name.indexOf('.'); |
| 970 var libraryName = name.substring(0, index); |
| 971 var remainingName = name.substring(index + 1); |
| 972 foundLibraryName(library) => library.uri.pathSegments[0] == libraryName; |
| 973 |
| 974 if (_sdkLibraries.any(foundLibraryName)) { |
| 975 var library = _sdkLibraries.singleWhere(foundLibraryName); |
| 976 // Look to see if it's a fully qualified library name. |
| 977 var scope = determineLookupFunc(remainingName)(library, remainingName); |
| 978 if (scope != null) { |
| 979 var result = getDocgenObject(scope); |
| 980 if (result is DummyMirror) { |
| 981 return packagePrefix + result.docName; |
| 982 } else { |
| 983 return result.packagePrefix + result.docName; |
| 984 } |
| 985 } |
| 986 } |
| 987 } |
| 988 return null; |
| 989 } |
| 990 |
| 991 Map expandMethodMap(Map<String, Method> mapToExpand) => { |
| 992 'setters': recurseMap(_filterMap(new Map(), mapToExpand, |
| 993 (key, val) => val.mirror.isSetter)), |
| 994 'getters': recurseMap(_filterMap(new Map(), mapToExpand, |
| 995 (key, val) => val.mirror.isGetter)), |
| 996 'constructors': recurseMap(_filterMap(new Map(), mapToExpand, |
| 997 (key, val) => val.mirror.isConstructor)), |
| 998 'operators': recurseMap(_filterMap(new Map(), mapToExpand, |
| 999 (key, val) => val.mirror.isOperator)), |
| 1000 'methods': recurseMap(_filterMap(new Map(), mapToExpand, |
| 1001 (key, val) => val.mirror.isRegularMethod && !val.mirror.isOperator)) |
| 1002 }; |
| 1003 |
| 1004 /// Transforms the map by calling toMap on each value in it. |
| 1005 Map recurseMap(Map inputMap) { |
| 1006 var outputMap = {}; |
| 1007 inputMap.forEach((key, value) { |
| 1008 if (value is Map) { |
| 1009 outputMap[key] = recurseMap(value); |
| 1010 } else { |
| 1011 outputMap[key] = value.toMap(); |
| 1012 } |
| 1013 }); |
| 1014 return outputMap; |
| 1015 } |
| 1016 |
| 1017 Map _filterMap(exported, map, test) { |
| 1018 map.forEach((key, value) { |
| 1019 if (test(key, value)) exported[key] = value; |
| 1020 }); |
| 1021 return exported; |
| 1022 } |
| 1023 |
| 1024 bool get _isVisible => _Generator._includePrivate || !isPrivate; |
| 884 } | 1025 } |
| 885 | 1026 |
| 886 /// A class containing contents of a Dart library. | 1027 /// A class containing contents of a Dart library. |
| 887 class Library extends Indexable { | 1028 class Library extends Indexable { |
| 888 | 1029 |
| 889 /// Top-level variables in the library. | 1030 /// Top-level variables in the library. |
| 890 Map<String, Variable> variables; | 1031 Map<String, Variable> variables; |
| 891 | 1032 |
| 892 /// Top-level functions in the library. | 1033 /// Top-level functions in the library. |
| 893 MethodGroup functions; | 1034 Map<String, Method> functions; |
| 894 | 1035 |
| 895 /// Classes defined within the library | 1036 Map<String, Class> classes = {}; |
| 896 ClassGroup classes; | 1037 Map<String, Typedef> typedefs = {}; |
| 1038 Map<String, Class> errors = {}; |
| 897 | 1039 |
| 898 String packageName = ''; | 1040 String packageName = ''; |
| 899 bool hasBeenCheckedForPackage = false; | 1041 bool hasBeenCheckedForPackage = false; |
| 900 String packageIntro; | 1042 String packageIntro; |
| 901 | 1043 |
| 902 Map<String, Exported> _exportedMembers; | 1044 /// Returns the [Library] for the given [mirror] if it has already been |
| 1045 /// created, else creates it. |
| 1046 factory Library(LibraryMirror mirror) { |
| 1047 var library = getDocgenObject(mirror); |
| 1048 if (library is DummyMirror) { |
| 1049 library = new Library._(mirror); |
| 1050 } |
| 1051 return library; |
| 1052 } |
| 903 | 1053 |
| 904 Library(LibraryMirror libraryMirror) : super(libraryMirror) { | 1054 Library._(LibraryMirror libraryMirror) : super(libraryMirror) { |
| 905 var exported = _calcExportedItems(libraryMirror); | 1055 var exported = _calcExportedItems(libraryMirror); |
| 906 _createClasses(exported['classes']..addAll(libraryMirror.classes)); | 1056 var exportedClasses = exported['classes']..addAll(libraryMirror.classes); |
| 907 this.functions = _createMethods( | 1057 _findPackage(mirror); |
| 908 exported['methods']..addAll(libraryMirror.functions), this); | 1058 classes = {}; |
| 909 this.variables = _createVariables( | 1059 typedefs = {}; |
| 910 exported['variables']..addAll(libraryMirror.variables), this); | 1060 errors = {}; |
| 1061 exportedClasses.forEach((String mirrorName, ClassMirror classMirror) { |
| 1062 if (classMirror.isTypedef) { |
| 1063 // This is actually a Dart2jsTypedefMirror, and it does define value, |
| 1064 // but we don't have visibility to that type. |
| 1065 var mirror = classMirror; |
| 1066 if (_Generator._includePrivate || !mirror.isPrivate) { |
| 1067 entityMap[getDocgenObject(mirror).docName] = |
| 1068 new Typedef(mirror, this); |
| 1069 typedefs[mirror.simpleName] = |
| 1070 entityMap[getDocgenObject(mirror).docName]; |
| 1071 } |
| 1072 } else { |
| 1073 var clazz = new Class(classMirror, this); |
| 911 | 1074 |
| 912 var exportedVariables = {}; | 1075 if (clazz.isError()) { |
| 913 variables.forEach((key, value) { | 1076 errors[classMirror.simpleName] = clazz; |
| 914 if (value is ExportedVariable) { | 1077 } else if (classMirror.isClass) { |
| 915 exportedVariables[key] = value; | 1078 classes[classMirror.simpleName] = clazz; |
| 916 } | 1079 } else { |
| 1080 throw new ArgumentError( |
| 1081 '${classMirror.simpleName} - no class type match. '); |
| 1082 } |
| 1083 } |
| 917 }); | 1084 }); |
| 918 _exportedMembers = new Map.from(this.classes.exported) | 1085 this.functions = _createMethods(exported['methods'] |
| 919 ..addAll(this.functions.exported) | 1086 ..addAll(libraryMirror.functions), this); |
| 920 ..addAll(exportedVariables); | 1087 this.variables = _createVariables(exported['variables'] |
| 1088 ..addAll(libraryMirror.variables), this); |
| 921 } | 1089 } |
| 922 | 1090 |
| 1091 /// Look for the specified name starting with the current member, and |
| 1092 /// progressively working outward to the current library scope. |
| 1093 String findElementInScope(String name) { |
| 1094 var lookupFunc = Indexable.determineLookupFunc(name); |
| 1095 var libraryScope = lookupFunc(mirror, name); |
| 1096 if (libraryScope != null) { |
| 1097 var result = getDocgenObject(libraryScope, this); |
| 1098 if (result is DummyMirror) return packagePrefix + result.docName; |
| 1099 return result.packagePrefix + result.docName; |
| 1100 } |
| 1101 return super.findElementInScope(name); |
| 1102 } |
| 1103 |
| 1104 /// For a library's [mirror], determine the name of the package (if any) we |
| 1105 /// believe it came from (because of its file URI). |
| 1106 /// |
| 1107 /// If no package could be determined, we return an empty string. |
| 1108 String _findPackage(LibraryMirror mirror) { |
| 1109 if (mirror == null) return ''; |
| 1110 if (hasBeenCheckedForPackage) return packageName; |
| 1111 hasBeenCheckedForPackage = true; |
| 1112 if (mirror.uri.scheme != 'file') return ''; |
| 1113 var filePath = mirror.uri.toFilePath(); |
| 1114 // We assume that we are documenting only libraries under package/lib |
| 1115 var rootdir = path.dirname((path.dirname(filePath))); |
| 1116 var pubspec = path.join(rootdir, 'pubspec.yaml'); |
| 1117 packageName = _packageName(pubspec); |
| 1118 // Associate the package readme with all the libraries. This is a bit |
| 1119 // wasteful, but easier than trying to figure out which partial match |
| 1120 // is best. |
| 1121 packageIntro = _packageIntro(rootdir); |
| 1122 return packageName; |
| 1123 } |
| 1124 |
| 1125 String _packageIntro(packageDir) { |
| 1126 var dir = new Directory(packageDir); |
| 1127 var files = dir.listSync(); |
| 1128 var readmes = files.where((FileSystemEntity each) => (each is File && |
| 1129 each.path.substring(packageDir.length + 1, each.path.length) |
| 1130 .startsWith('README'))).toList(); |
| 1131 if (readmes.isEmpty) return ''; |
| 1132 // If there are multiples, pick the shortest name. |
| 1133 readmes.sort((a, b) => a.path.length.compareTo(b.path.length)); |
| 1134 var readme = readmes.first; |
| 1135 var linkResolver = (name) => Indexable.globalFixReference(name); |
| 1136 var contents = markdown.markdownToHtml(readme |
| 1137 .readAsStringSync(), linkResolver: linkResolver, |
| 1138 inlineSyntaxes: _MARKDOWN_SYNTAXES); |
| 1139 return contents; |
| 1140 } |
| 1141 |
| 1142 /// Read a pubspec and return the library name. |
| 1143 String _packageName(String pubspecName) { |
| 1144 File pubspec = new File(pubspecName); |
| 1145 if (!pubspec.existsSync()) return ''; |
| 1146 var contents = pubspec.readAsStringSync(); |
| 1147 var spec = loadYaml(contents); |
| 1148 return spec["name"]; |
| 1149 } |
| 1150 |
| 1151 markdown.Node fixReferenceWithScope(String name) => fixReference(name); |
| 1152 |
| 923 String get packagePrefix => packageName == null || packageName.isEmpty ? | 1153 String get packagePrefix => packageName == null || packageName.isEmpty ? |
| 924 '' : '$packageName/'; | 1154 '' : '$packageName/'; |
| 925 | 1155 |
| 926 Map get previewMap { | 1156 Map get previewMap { |
| 927 var basic = super.previewMap; | 1157 var basic = super.previewMap; |
| 928 basic['packageName'] = packageName; | 1158 basic['packageName'] = packageName; |
| 929 if (packageIntro != null) { | 1159 if (packageIntro != null) { |
| 930 basic['packageIntro'] = packageIntro; | 1160 basic['packageIntro'] = packageIntro; |
| 931 } | 1161 } |
| 932 return basic; | 1162 return basic; |
| 933 } | 1163 } |
| 934 | 1164 |
| 935 String get owner => ''; | 1165 String get name => docName; |
| 936 | 1166 |
| 937 String get name => docName(mirror); | 1167 String get docName => mirror.qualifiedName.replaceAll('.','-'); |
| 938 | |
| 939 /// Set our classes field with error, typedef and regular classes. | |
| 940 void _createClasses(Map<String, ClassMirror> mirrorMap) { | |
| 941 this.classes = new ClassGroup(); | |
| 942 mirrorMap.forEach((String mirrorName, ClassMirror mirror) { | |
| 943 this.classes.addClass(mirror, this); | |
| 944 }); | |
| 945 } | |
| 946 | 1168 |
| 947 /// For the given library determine what items (if any) are exported. | 1169 /// For the given library determine what items (if any) are exported. |
| 948 /// | 1170 /// |
| 949 /// Returns a Map with three keys: "classes", "methods", and "variables" the | 1171 /// Returns a Map with three keys: "classes", "methods", and "variables" the |
| 950 /// values of which point to a map of exported name identifiers with values | 1172 /// values of which point to a map of exported name identifiers with values |
| 951 /// corresponding to the actual DeclarationMirror. | 1173 /// corresponding to the actual DeclarationMirror. |
| 952 Map<String, Map<String, DeclarationMirror>> _calcExportedItems( | 1174 Map<String, Map<String, DeclarationMirror>> _calcExportedItems( |
| 953 LibraryMirror library) { | 1175 LibraryMirror library) { |
| 954 var exports = {}; | 1176 var exports = {}; |
| 955 exports['classes'] = {}; | 1177 exports['classes'] = {}; |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 999 // If there is a show in the export, add only the show items to the | 1221 // If there is a show in the export, add only the show items to the |
| 1000 // library. Ex: "export foo show bar" | 1222 // library. Ex: "export foo show bar" |
| 1001 // Otherwise, add all items, and then remove the hidden ones. | 1223 // Otherwise, add all items, and then remove the hidden ones. |
| 1002 // Ex: "export foo hide bar" | 1224 // Ex: "export foo hide bar" |
| 1003 _populateExports(export, | 1225 _populateExports(export, |
| 1004 export.combinators.any((combinator) => combinator.isShow)); | 1226 export.combinators.any((combinator) => combinator.isShow)); |
| 1005 } | 1227 } |
| 1006 return exports; | 1228 return exports; |
| 1007 } | 1229 } |
| 1008 | 1230 |
| 1231 /// Checks if the given name is a key for any of the Class Maps. |
| 1232 bool containsKey(String name) { |
| 1233 return classes.containsKey(name) || errors.containsKey(name); |
| 1234 } |
| 1235 |
| 1009 /// Generates a map describing the [Library] object. | 1236 /// Generates a map describing the [Library] object. |
| 1010 Map toMap() => { | 1237 Map toMap() => { |
| 1011 'name': name, | 1238 'name': name, |
| 1012 'qualifiedName': qualifiedName, | 1239 'qualifiedName': qualifiedName, |
| 1013 'comment': comment, | 1240 'comment': comment, |
| 1014 'variables': recurseMap(variables), | 1241 'variables': recurseMap(variables), |
| 1015 'functions': functions.toMap(), | 1242 'functions': expandMethodMap(functions), |
| 1016 'classes': classes.toMap(), | 1243 'classes': { |
| 1244 'class': classes.values.where((c) => c._isVisible) |
| 1245 .map((e) => e.previewMap).toList(), |
| 1246 'typedef': recurseMap(typedefs), |
| 1247 'error': errors.values.where((e) => e._isVisible) |
| 1248 .map((e) => e.previewMap).toList() |
| 1249 }, |
| 1017 'packageName': packageName, | 1250 'packageName': packageName, |
| 1018 'packageIntro' : packageIntro | 1251 'packageIntro' : packageIntro |
| 1019 }; | 1252 }; |
| 1020 | 1253 |
| 1021 String get typeName => 'library'; | 1254 String get typeName => 'library'; |
| 1022 } | 1255 } |
| 1023 | 1256 |
| 1024 /// A class containing contents of a Dart class. | 1257 /// A class containing contents of a Dart class. |
| 1025 class Class extends Indexable implements Comparable { | 1258 class Class extends Indexable implements Comparable { |
| 1026 | 1259 |
| 1027 /// List of the names of interfaces that this class implements. | 1260 /// List of the names of interfaces that this class implements. |
| 1028 List<Class> interfaces = []; | 1261 List<Class> interfaces = []; |
| 1029 | 1262 |
| 1030 /// Names of classes that extends or implements this class. | 1263 /// Names of classes that extends or implements this class. |
| 1031 Set<Class> subclasses = new Set<Class>(); | 1264 Set<Class> subclasses = new Set<Class>(); |
| 1032 | 1265 |
| 1033 /// Top-level variables in the class. | 1266 /// Top-level variables in the class. |
| 1034 Map<String, Variable> variables; | 1267 Map<String, Variable> variables; |
| 1035 | 1268 |
| 1036 /// Inherited variables in the class. | 1269 /// Inherited variables in the class. |
| 1037 Map<String, Variable> inheritedVariables = {}; | 1270 Map<String, Variable> inheritedVariables; |
| 1038 | 1271 |
| 1039 /// Methods in the class. | 1272 /// Methods in the class. |
| 1040 MethodGroup methods; | 1273 Map<String, Method> methods; |
| 1041 | 1274 |
| 1042 /// Inherited methods in the class. | 1275 Map<String, Method> inheritedMethods; |
| 1043 MethodGroup inheritedMethods = new MethodGroup(); | |
| 1044 | 1276 |
| 1045 /// Generic infomation about the class. | 1277 /// Generic infomation about the class. |
| 1046 Map<String, Generic> generics; | 1278 Map<String, Generic> generics; |
| 1047 | 1279 |
| 1048 Class superclass; | 1280 Class superclass; |
| 1049 bool isAbstract; | 1281 bool isAbstract; |
| 1050 | 1282 |
| 1051 /// List of the meta annotations on the class. | 1283 /// List of the meta annotations on the class. |
| 1052 List<Annotation> annotations; | 1284 List<Annotation> annotations; |
| 1053 | 1285 |
| 1054 /// Make sure that we don't check for inherited comments more than once. | 1286 /// Make sure that we don't check for inherited comments more than once. |
| 1055 bool _commentsEnsured = false; | 1287 bool _commentsEnsured = false; |
| 1056 | 1288 |
| 1289 Indexable owner; |
| 1290 |
| 1057 /// Returns the [Class] for the given [mirror] if it has already been created, | 1291 /// Returns the [Class] for the given [mirror] if it has already been created, |
| 1058 /// else creates it. | 1292 /// else creates it. |
| 1059 factory Class(ClassMirror mirror) { | 1293 factory Class(ClassMirror mirror, Library owner) { |
| 1060 var clazz = entityMap[docName(mirror)]; | 1294 var clazz = getDocgenObject(mirror, owner); |
| 1061 if (clazz == null) { | 1295 if (clazz is DummyMirror) { |
| 1062 clazz = new Class._(mirror); | 1296 clazz = new Class._(mirror, owner); |
| 1063 entityMap[docName(mirror)] = clazz; | 1297 entityMap[clazz.docName] = clazz; |
| 1064 } | 1298 } |
| 1065 return clazz; | 1299 return clazz; |
| 1066 } | 1300 } |
| 1067 | 1301 |
| 1068 Class._(ClassMirror classMirror) : super(classMirror) { | 1302 /// Called when we are constructing a superclass or interface class, but it |
| 1069 var superclass = classMirror.superclass != null ? | 1303 /// is not known if it belongs to the same owner as the original class. In |
| 1070 new Class(classMirror.superclass) : null; | 1304 /// this case, we create an object whose owner is what the original mirror |
| 1071 var interfaces = classMirror.superinterfaces.map( | 1305 /// says it is. |
| 1072 (interface) => new Class(interface)); | 1306 factory Class._possiblyDifferentOwner(ClassMirror mirror, |
| 1307 Library originalOwner) { |
| 1308 if (mirror.owner is LibraryMirror) { |
| 1309 var realOwner = getDocgenObject(mirror.owner); |
| 1310 if (realOwner is Library) { |
| 1311 return new Class(mirror, realOwner); |
| 1312 } else { |
| 1313 return new Class(mirror, originalOwner); |
| 1314 } |
| 1315 } else { |
| 1316 return new Class(mirror, originalOwner); |
| 1317 } |
| 1318 } |
| 1073 | 1319 |
| 1074 this.superclass = superclass; | 1320 Class._(ClassMirror classMirror, this.owner) : super(classMirror) { |
| 1075 this.interfaces = interfaces.toList(); | 1321 inheritedVariables = {}; |
| 1076 this.variables = _createVariables(classMirror.variables); | |
| 1077 this.methods = _createMethods(classMirror.methods); | |
| 1078 this.annotations = _createAnnotations(classMirror); | |
| 1079 this.generics = _createGenerics(classMirror); | |
| 1080 this.isAbstract = classMirror.isAbstract; | |
| 1081 | 1322 |
| 1082 // Tell all superclasses that you are a subclass. | 1323 // The reason we do this madness is the superclass and interface owners may |
| 1083 if (!classMirror.isNameSynthetic && _isVisible(this)) { | 1324 // not be this class's owner!! Example: BaseClient in http pkg. |
| 1325 var superinterfaces = classMirror.superinterfaces.map( |
| 1326 (interface) => new Class._possiblyDifferentOwner(interface, owner)); |
| 1327 this.superclass = classMirror.superclass == null? null : |
| 1328 new Class._possiblyDifferentOwner(classMirror.superclass, owner); |
| 1329 |
| 1330 interfaces = superinterfaces.toList(); |
| 1331 variables = _createVariables(classMirror.variables, this); |
| 1332 methods = _createMethods(classMirror.methods, this); |
| 1333 annotations = _createAnnotations(classMirror, this); |
| 1334 generics = _createGenerics(classMirror); |
| 1335 isAbstract = classMirror.isAbstract; |
| 1336 inheritedMethods = new Map<String, Method>(); |
| 1337 |
| 1338 // Tell all superclasses that you are a subclass, unless you are not |
| 1339 // visible or an intermediary mixin class. |
| 1340 if (!classMirror.isNameSynthetic && _isVisible) { |
| 1084 parentChain().forEach((parentClass) { | 1341 parentChain().forEach((parentClass) { |
| 1085 parentClass.addSubclass(this); | 1342 parentClass.addSubclass(this); |
| 1086 }); | 1343 }); |
| 1087 } | 1344 } |
| 1088 | 1345 |
| 1089 if (this.superclass != null) addInherited(superclass); | 1346 if (this.superclass != null) addInherited(superclass); |
| 1090 interfaces.forEach((interface) => addInherited(interface)); | 1347 interfaces.forEach((interface) => addInherited(interface)); |
| 1091 } | 1348 } |
| 1092 | 1349 |
| 1350 String get packagePrefix => owner.packagePrefix; |
| 1351 |
| 1352 String _lookupInClassAndSuperclasses(String name) { |
| 1353 var lookupFunc = Indexable.determineLookupFunc(name); |
| 1354 var classScope = this; |
| 1355 while (classScope != null) { |
| 1356 var classFunc = lookupFunc(classScope.mirror, name); |
| 1357 if (classFunc != null) { |
| 1358 return packagePrefix + getDocgenObject(classFunc, owner).docName; |
| 1359 } |
| 1360 classScope = classScope.superclass; |
| 1361 } |
| 1362 return null; |
| 1363 } |
| 1364 |
| 1365 /// Look for the specified name starting with the current member, and |
| 1366 /// progressively working outward to the current library scope. |
| 1367 String findElementInScope(String name) { |
| 1368 var lookupFunc = Indexable.determineLookupFunc(name); |
| 1369 var result = _lookupInClassAndSuperclasses(name); |
| 1370 if (result != null) { |
| 1371 return result; |
| 1372 } |
| 1373 result = owner.findElementInScope(name); |
| 1374 return result == null ? super.findElementInScope(name) : result; |
| 1375 } |
| 1376 |
| 1377 markdown.Node fixReferenceWithScope(String name) => fixReference(name); |
| 1378 |
| 1093 String get typeName => 'class'; | 1379 String get typeName => 'class'; |
| 1094 | 1380 |
| 1095 /// Returns a list of all the parent classes. | 1381 /// Returns a list of all the parent classes. |
| 1096 List<Class> parentChain() { | 1382 List<Class> parentChain() { |
| 1097 var parent = superclass == null ? [] : [superclass]; | 1383 var parent = superclass == null ? [] : [superclass]; |
| 1098 parent.addAll(interfaces); | 1384 parent.addAll(interfaces); |
| 1099 return parent; | 1385 return parent; |
| 1100 } | 1386 } |
| 1101 | 1387 |
| 1102 /// Add all inherited variables and methods from the provided superclass. | 1388 /// Add all inherited variables and methods from the provided superclass. |
| 1103 /// If [_includePrivate] is true, it also adds the variables and methods from | 1389 /// If [_includePrivate] is true, it also adds the variables and methods from |
| 1104 /// the superclass. | 1390 /// the superclass. |
| 1105 void addInherited(Class superclass) { | 1391 void addInherited(Class superclass) { |
| 1106 inheritedVariables.addAll(superclass.inheritedVariables); | 1392 inheritedVariables.addAll(superclass.inheritedVariables); |
| 1107 inheritedVariables.addAll(_filterStatics(superclass.variables)); | 1393 inheritedVariables.addAll(_allButStatics(superclass.variables)); |
| 1108 inheritedMethods.addInherited(superclass); | 1394 addInheritedMethod(superclass, this); |
| 1395 } |
| 1396 |
| 1397 /** [newParent] refers to the actual class is currently using these methods. |
| 1398 * which may be different because with the mirror system, we only point to the |
| 1399 * original canonical superclasse's method. |
| 1400 */ |
| 1401 void addInheritedMethod(Class parent, Class newParent) { |
| 1402 parent.inheritedMethods.forEach((name, method) { |
| 1403 if(!method.isConstructor){ |
| 1404 inheritedMethods[name] = new Method(method.mirror, newParent, method); |
| 1405 }} |
| 1406 ); |
| 1407 _allButStatics(parent.methods).forEach((name, method) { |
| 1408 if (!method.isConstructor) { |
| 1409 inheritedMethods[name] = new Method(method.mirror, newParent, method); |
| 1410 }} |
| 1411 ); |
| 1412 } |
| 1413 |
| 1414 /// Remove statics from the map of inherited items before adding them. |
| 1415 Map _allButStatics(Map items) { |
| 1416 var result = {}; |
| 1417 items.forEach((name, item) { |
| 1418 if (!item.isStatic) { |
| 1419 result[name] = item; |
| 1420 } |
| 1421 }); |
| 1422 return result; |
| 1109 } | 1423 } |
| 1110 | 1424 |
| 1111 /// Add the subclass to the class. | 1425 /// Add the subclass to the class. |
| 1112 /// | 1426 /// |
| 1113 /// If [this] is private, it will add the subclass to the list of subclasses | 1427 /// If [this] is private (or an intermediary mixin class), it will add the |
| 1114 /// in the superclasses. | 1428 /// subclass to the list of subclasses in the superclasses. |
| 1115 void addSubclass(Class subclass) { | 1429 void addSubclass(Class subclass) { |
| 1116 if (!_includePrivate && isPrivate) { | 1430 if (docName == 'dart-core.Object') return; |
| 1431 |
| 1432 if (!_Generator._includePrivate && isPrivate || mirror.isNameSynthetic) { |
| 1117 if (superclass != null) superclass.addSubclass(subclass); | 1433 if (superclass != null) superclass.addSubclass(subclass); |
| 1118 interfaces.forEach((interface) { | 1434 interfaces.forEach((interface) { |
| 1119 interface.addSubclass(subclass); | 1435 interface.addSubclass(subclass); |
| 1120 }); | 1436 }); |
| 1121 } else { | 1437 } else { |
| 1122 subclasses.add(subclass); | 1438 subclasses.add(subclass); |
| 1123 } | 1439 } |
| 1124 } | 1440 } |
| 1125 | 1441 |
| 1126 /// Check if this [Class] is an error or exception. | 1442 /// Check if this [Class] is an error or exception. |
| 1127 bool isError() { | 1443 bool isError() { |
| 1128 if (qualifiedName == 'dart-core.Error' || | 1444 if (qualifiedName == 'dart-core.Error' || |
| 1129 qualifiedName == 'dart-core.Exception') | 1445 qualifiedName == 'dart-core.Exception') |
| 1130 return true; | 1446 return true; |
| 1131 for (var interface in interfaces) { | 1447 for (var interface in interfaces) { |
| 1132 if (interface.isError()) return true; | 1448 if (interface.isError()) return true; |
| 1133 } | 1449 } |
| 1134 if (superclass == null) return false; | 1450 if (superclass == null) return false; |
| 1135 return superclass.isError(); | 1451 return superclass.isError(); |
| 1136 } | 1452 } |
| 1137 | 1453 |
| 1138 /// Check that the class exists in the owner library. | |
| 1139 /// | |
| 1140 /// If it does not exist in the owner library, it is a mixin applciation and | |
| 1141 /// should be removed. | |
| 1142 void updateLinksAndRemoveIntermediaryClasses() { | |
| 1143 var library = entityMap[owner]; | |
| 1144 if (library != null) { | |
| 1145 if (!library.classes.containsKey(name) && mirror.isNameSynthetic) { | |
| 1146 // In the mixin case, remove the intermediary classes. | |
| 1147 this.isPrivate = true; | |
| 1148 // Since we are now making the mixin a private class, make all elements | |
| 1149 // with the mixin as an owner private too. | |
| 1150 entityMap.values.where((e) => e.owner == qualifiedName).forEach( | |
| 1151 (element) => element.isPrivate = true); | |
| 1152 // Move the subclass up to the next public superclass | |
| 1153 subclasses.forEach((subclass) => addSubclass(subclass)); | |
| 1154 } else { | |
| 1155 // It is an exported item. Loop through each of the exported types, | |
| 1156 // and tell them to update their links, given these other exported | |
| 1157 // names within the library. | |
| 1158 for (Exported member in library._exportedMembers.values) { | |
| 1159 member.updateExports(library._exportedMembers); | |
| 1160 } | |
| 1161 } | |
| 1162 } | |
| 1163 } | |
| 1164 | |
| 1165 /// Makes sure that all methods with inherited equivalents have comments. | 1454 /// Makes sure that all methods with inherited equivalents have comments. |
| 1166 void ensureComments() { | 1455 void ensureComments() { |
| 1167 if (_commentsEnsured) return; | 1456 if (_commentsEnsured) return; |
| 1168 _commentsEnsured = true; | 1457 _commentsEnsured = true; |
| 1458 if (superclass != null) superclass.ensureComments(); |
| 1169 inheritedMethods.forEach((qualifiedName, inheritedMethod) { | 1459 inheritedMethods.forEach((qualifiedName, inheritedMethod) { |
| 1170 var method = methods[qualifiedName]; | 1460 var method = methods[qualifiedName]; |
| 1171 if (method != null) method.ensureCommentFor(inheritedMethod); | 1461 if (method != null) { |
| 1462 // if we have overwritten this method in this class, we still provide |
| 1463 // the opportunity to inherit the comments. |
| 1464 method.ensureCommentFor(inheritedMethod); |
| 1465 } |
| 1466 }); |
| 1467 // we need to populate the comments for all methods. so that the subclasses |
| 1468 // can get for their inherited versions the comments. |
| 1469 methods.forEach((qualifiedName, method) { |
| 1470 if (!method.mirror.isConstructor) method.ensureCommentFor(method); |
| 1172 }); | 1471 }); |
| 1173 } | 1472 } |
| 1174 | 1473 |
| 1175 /// If a class extends a private superclass, find the closest public | 1474 /// If a class extends a private superclass, find the closest public |
| 1176 /// superclass of the private superclass. | 1475 /// superclass of the private superclass. |
| 1177 String validSuperclass() { | 1476 String validSuperclass() { |
| 1178 if (superclass == null) return 'dart.core.Object'; | 1477 if (superclass == null) return 'dart.core.Object'; |
| 1179 if (_isVisible(superclass)) return superclass.qualifiedName; | 1478 if (superclass._isVisible) return superclass.qualifiedName; |
| 1180 return superclass.validSuperclass(); | 1479 return superclass.validSuperclass(); |
| 1181 } | 1480 } |
| 1182 | 1481 |
| 1183 /// Generates a map describing the [Class] object. | 1482 /// Generates a map describing the [Class] object. |
| 1184 Map toMap() => { | 1483 Map toMap() => { |
| 1185 'name': name, | 1484 'name': name, |
| 1186 'qualifiedName': qualifiedName, | 1485 'qualifiedName': qualifiedName, |
| 1187 'comment': comment, | 1486 'comment': comment, |
| 1188 'isAbstract' : isAbstract, | 1487 'isAbstract' : isAbstract, |
| 1189 'superclass': validSuperclass(), | 1488 'superclass': validSuperclass(), |
| 1190 'implements': interfaces.where(_isVisible) | 1489 'implements': interfaces.where((i) => i._isVisible) |
| 1191 .map((e) => e.qualifiedName).toList(), | 1490 .map((e) => e.qualifiedName).toList(), |
| 1192 'subclass': (subclasses.toList()..sort()) | 1491 'subclass': (subclasses.toList()..sort()) |
| 1193 .map((x) => x.qualifiedName).toList(), | 1492 .map((x) => x.qualifiedName).toList(), |
| 1194 'variables': recurseMap(variables), | 1493 'variables': recurseMap(variables), |
| 1195 'inheritedVariables': recurseMap(inheritedVariables), | 1494 'inheritedVariables': recurseMap(inheritedVariables), |
| 1196 'methods': methods.toMap(), | 1495 'methods': expandMethodMap(methods), |
| 1197 'inheritedMethods': inheritedMethods.toMap(), | 1496 'inheritedMethods': expandMethodMap(inheritedMethods), |
| 1198 'annotations': annotations.map((a) => a.toMap()).toList(), | 1497 'annotations': annotations.map((a) => a.toMap()).toList(), |
| 1199 'generics': recurseMap(generics) | 1498 'generics': recurseMap(generics) |
| 1200 }; | 1499 }; |
| 1201 | 1500 |
| 1202 int compareTo(aClass) => name.compareTo(aClass.name); | 1501 int compareTo(aClass) => name.compareTo(aClass.name); |
| 1203 } | 1502 } |
| 1204 | 1503 |
| 1205 abstract class Exported { | |
| 1206 void updateExports(Map<String, Indexable> libraryExports); | |
| 1207 } | |
| 1208 | |
| 1209 Map _filterMap(exported, map, test) { | |
| 1210 map.forEach((key, value) { | |
| 1211 if (test(value)) exported[key] = value; | |
| 1212 }); | |
| 1213 return exported; | |
| 1214 } | |
| 1215 | |
| 1216 class ExportedClass extends Class implements Exported { | |
| 1217 Class _originalClass; | |
| 1218 Library _exportingLibrary; | |
| 1219 | |
| 1220 ExportedClass(ClassMirror originalClass, Library this._exportingLibrary) : | |
| 1221 super._(originalClass) { | |
| 1222 _originalClass = new Class(originalClass); | |
| 1223 } | |
| 1224 | |
| 1225 // The qualified name (for URL purposes) and the file name are the same, | |
| 1226 // of the form packageName/ClassName or packageName/ClassName.methodName. | |
| 1227 // This defines both the URL and the directory structure. | |
| 1228 String get fileName => path.join(_exportingLibrary.packageName, | |
| 1229 _exportingLibrary.mirror.qualifiedName + '.' + _originalClass.name); | |
| 1230 | |
| 1231 void updateExports(Map<String, Indexable> libraryExports) { | |
| 1232 // TODO(efortuna): If this class points to another exported class or type | |
| 1233 // of some sort, then that reference needs to be updated here. | |
| 1234 /* these need to be updated: | |
| 1235 'comment': comment, | |
| 1236 'superclass': validSuperclass(), | |
| 1237 'implements': interfaces.where(_isVisible) | |
| 1238 .map((e) => e.qualifiedName).toList(), | |
| 1239 'subclass': (subclasses.toList()..sort()) | |
| 1240 .map((x) => x.qualifiedName).toList(), | |
| 1241 'variables': recurseMap(variables), | |
| 1242 'inheritedVariables': recurseMap(inheritedVariables), | |
| 1243 'methods': methods.toMap(), | |
| 1244 'inheritedMethods': inheritedMethods.toMap(), | |
| 1245 'annotations': annotations.map((a) => a.toMap()).toList(), | |
| 1246 'generics': recurseMap(generics) | |
| 1247 */ | |
| 1248 } | |
| 1249 } | |
| 1250 | |
| 1251 /// A container to categorize classes into the following groups: abstract | |
| 1252 /// classes, regular classes, typedefs, and errors. | |
| 1253 class ClassGroup { | |
| 1254 Map<String, Class> classes = {}; | |
| 1255 Map<String, Typedef> typedefs = {}; | |
| 1256 Map<String, Class> errors = {}; | |
| 1257 | |
| 1258 Map<String, Exported> get exported { | |
| 1259 var exported = _filterMap({}, classes, (value) => value is ExportedClass); | |
| 1260 // TODO(efortuna): The line below needs updating. | |
| 1261 exported = _filterMap(exported, typedefs, | |
| 1262 (value) => value is ExportedClass); | |
| 1263 exported = _filterMap(exported, errors, | |
| 1264 (value) => value is ExportedClass); | |
| 1265 return exported; | |
| 1266 } | |
| 1267 | |
| 1268 void addClass(ClassMirror classMirror, Library containingLibrary) { | |
| 1269 if (classMirror.isTypedef) { | |
| 1270 // This is actually a Dart2jsTypedefMirror, and it does define value, | |
| 1271 // but we don't have visibility to that type. | |
| 1272 var mirror = classMirror; | |
| 1273 if (_includePrivate || !mirror.isPrivate) { | |
| 1274 entityMap[docName(mirror)] = new Typedef(mirror); | |
| 1275 typedefs[mirror.simpleName] = entityMap[docName(mirror)]; | |
| 1276 } | |
| 1277 } else { | |
| 1278 var clazz = new Class(classMirror); | |
| 1279 | |
| 1280 if (classMirror.library.qualifiedName != | |
| 1281 containingLibrary.mirror.qualifiedName) { | |
| 1282 var exportedClass = new ExportedClass(classMirror, containingLibrary); | |
| 1283 entityMap[clazz.fileName] = exportedClass; | |
| 1284 clazz = exportedClass; | |
| 1285 } | |
| 1286 | |
| 1287 if (clazz.isError()) { | |
| 1288 errors[classMirror.simpleName] = clazz; | |
| 1289 } else if (classMirror.isClass) { | |
| 1290 classes[classMirror.simpleName] = clazz; | |
| 1291 } else { | |
| 1292 throw new ArgumentError( | |
| 1293 '${classMirror.simpleName} - no class type match. '); | |
| 1294 } | |
| 1295 } | |
| 1296 } | |
| 1297 | |
| 1298 /// Checks if the given name is a key for any of the Class Maps. | |
| 1299 bool containsKey(String name) { | |
| 1300 return classes.containsKey(name) || errors.containsKey(name); | |
| 1301 } | |
| 1302 | |
| 1303 Map toMap() => { | |
| 1304 'class': classes.values.where(_isVisible) | |
| 1305 .map((e) => e.previewMap).toList(), | |
| 1306 'typedef': recurseMap(typedefs), | |
| 1307 'error': errors.values.where(_isVisible) | |
| 1308 .map((e) => e.previewMap).toList() | |
| 1309 }; | |
| 1310 } | |
| 1311 | |
| 1312 class Typedef extends Indexable { | 1504 class Typedef extends Indexable { |
| 1313 String returnType; | 1505 String returnType; |
| 1314 | 1506 |
| 1315 Map<String, Parameter> parameters; | 1507 Map<String, Parameter> parameters; |
| 1316 | 1508 |
| 1317 /// Generic information about the typedef. | 1509 /// Generic information about the typedef. |
| 1318 Map<String, Generic> generics; | 1510 Map<String, Generic> generics; |
| 1319 | 1511 |
| 1320 /// List of the meta annotations on the typedef. | 1512 /// List of the meta annotations on the typedef. |
| 1321 List<Annotation> annotations; | 1513 List<Annotation> annotations; |
| 1322 | 1514 |
| 1323 Typedef(mirror) : super(mirror) { | 1515 /// Returns the [Library] for the given [mirror] if it has already been |
| 1324 this.returnType = docName(mirror.value.returnType); | 1516 /// created, else creates it. |
| 1325 this.generics = _createGenerics(mirror); | 1517 factory Typedef(TypedefMirror mirror, Library owningLibrary) { |
| 1326 this.parameters = _createParameters(mirror.value.parameters); | 1518 var aTypedef = getDocgenObject(mirror, owningLibrary); |
| 1327 this.annotations = _createAnnotations(mirror); | 1519 if (aTypedef is DummyMirror) { |
| 1520 aTypedef = new Typedef._(mirror, owningLibrary); |
| 1521 } |
| 1522 return aTypedef; |
| 1523 } |
| 1524 |
| 1525 Typedef._(TypedefMirror mirror, Library owningLibrary) : super(mirror) { |
| 1526 owner = owningLibrary; |
| 1527 returnType = getDocgenObject(mirror.value.returnType).docName; |
| 1528 generics = _createGenerics(mirror); |
| 1529 parameters = _createParameters(mirror.value.parameters); |
| 1530 annotations = _createAnnotations(mirror, this); |
| 1328 } | 1531 } |
| 1329 | 1532 |
| 1330 Map toMap() => { | 1533 Map toMap() => { |
| 1331 'name': name, | 1534 'name': name, |
| 1332 'qualifiedName': qualifiedName, | 1535 'qualifiedName': qualifiedName, |
| 1333 'comment': comment, | 1536 'comment': comment, |
| 1334 'return': returnType, | 1537 'return': returnType, |
| 1335 'parameters': recurseMap(parameters), | 1538 'parameters': recurseMap(parameters), |
| 1336 'annotations': annotations.map((a) => a.toMap()).toList(), | 1539 'annotations': annotations.map((a) => a.toMap()).toList(), |
| 1337 'generics': recurseMap(generics) | 1540 'generics': recurseMap(generics) |
| 1338 }; | 1541 }; |
| 1339 | 1542 |
| 1340 String get typeName => 'typedef'; | 1543 String get typeName => 'typedef'; |
| 1341 } | 1544 } |
| 1342 | 1545 |
| 1343 /// A class containing properties of a Dart variable. | 1546 /// A class containing properties of a Dart variable. |
| 1344 class Variable extends Indexable { | 1547 class Variable extends Indexable { |
| 1345 | 1548 |
| 1346 bool isFinal; | 1549 bool isFinal; |
| 1347 bool isStatic; | 1550 bool isStatic; |
| 1348 bool isConst; | 1551 bool isConst; |
| 1349 Type type; | 1552 Type type; |
| 1350 String _variableName; | 1553 String _variableName; |
| 1554 Indexable owner; |
| 1351 | 1555 |
| 1352 /// List of the meta annotations on the variable. | 1556 /// List of the meta annotations on the variable. |
| 1353 List<Annotation> annotations; | 1557 List<Annotation> annotations; |
| 1354 | 1558 |
| 1355 Variable(this._variableName, VariableMirror mirror) : super(mirror) { | 1559 factory Variable(String variableName, VariableMirror mirror, |
| 1356 this.isFinal = mirror.isFinal; | 1560 Indexable owner) { |
| 1357 this.isStatic = mirror.isStatic; | 1561 var variable = getDocgenObject(mirror); |
| 1358 this.isConst = mirror.isConst; | 1562 if (variable is DummyMirror) { |
| 1359 this.type = _createType(mirror.type); | 1563 return new Variable._(variableName, mirror, owner); |
| 1360 this.annotations = _createAnnotations(mirror); | 1564 } |
| 1565 return variable; |
| 1566 } |
| 1567 |
| 1568 Variable._(this._variableName, VariableMirror mirror, this.owner) : |
| 1569 super(mirror) { |
| 1570 isFinal = mirror.isFinal; |
| 1571 isStatic = mirror.isStatic; |
| 1572 isConst = mirror.isConst; |
| 1573 type = new Type(mirror.type, _getOwningLibrary(owner)); |
| 1574 annotations = _createAnnotations(mirror, _getOwningLibrary(owner)); |
| 1361 } | 1575 } |
| 1362 | 1576 |
| 1363 String get name => _variableName; | 1577 String get name => _variableName; |
| 1364 | 1578 |
| 1365 /// Generates a map describing the [Variable] object. | 1579 /// Generates a map describing the [Variable] object. |
| 1366 Map toMap() => { | 1580 Map toMap() => { |
| 1367 'name': name, | 1581 'name': name, |
| 1368 'qualifiedName': qualifiedName, | 1582 'qualifiedName': qualifiedName, |
| 1369 'comment': comment, | 1583 'comment': comment, |
| 1370 'final': isFinal.toString(), | 1584 'final': isFinal.toString(), |
| 1371 'static': isStatic.toString(), | 1585 'static': isStatic.toString(), |
| 1372 'constant': isConst.toString(), | 1586 'constant': isConst.toString(), |
| 1373 'type': new List.filled(1, type.toMap()), | 1587 'type': new List.filled(1, type.toMap()), |
| 1374 'annotations': annotations.map((a) => a.toMap()).toList() | 1588 'annotations': annotations.map((a) => a.toMap()).toList() |
| 1375 }; | 1589 }; |
| 1376 | 1590 |
| 1591 String get packagePrefix => owner.packagePrefix; |
| 1592 |
| 1377 String get typeName => 'property'; | 1593 String get typeName => 'property'; |
| 1378 | 1594 |
| 1379 get comment { | 1595 get comment { |
| 1380 if (_comment != null) return _comment; | 1596 if (_comment != null) return _comment; |
| 1381 var owningClass = owningEntity; | 1597 if (owner is Class) { |
| 1382 if (owningClass is Class) { | 1598 (owner as Class).ensureComments(); |
| 1383 owningClass.ensureComments(); | |
| 1384 } | 1599 } |
| 1385 return super.comment; | 1600 return super.comment; |
| 1386 } | 1601 } |
| 1387 } | |
| 1388 | 1602 |
| 1389 class ExportedVariable extends Variable implements Exported { | 1603 markdown.Node fixReferenceWithScope(String name) => fixReference(name); |
| 1390 Library _exportingLibrary; | |
| 1391 | 1604 |
| 1392 ExportedVariable(String variableName, VariableMirror originalVariable, | 1605 String findElementInScope(String name) { |
| 1393 Library this._exportingLibrary) : super(variableName, originalVariable); | 1606 var lookupFunc = Indexable.determineLookupFunc(name); |
| 1607 var result = lookupFunc(mirror, name); |
| 1608 if (result != null) { |
| 1609 result = getDocgenObject(result); |
| 1610 if (result is DummyMirror) return packagePrefix + result.docName; |
| 1611 return result.packagePrefix + result.docName; |
| 1612 } |
| 1394 | 1613 |
| 1395 String get fileName => path.join(_exportingLibrary.packageName, | 1614 if (owner != null) { |
| 1396 _exportingLibrary.mirror.qualifiedName + '.' + super.name); | 1615 var result = owner.findElementInScope(name); |
| 1397 | 1616 if (result != null) { |
| 1398 void updateExports(Map<String, Indexable> libraryExports) { | 1617 return result; |
| 1399 // TODO(efortuna): if this class points to another exported class or type | 1618 } |
| 1400 // of some sort, then that reference needs to be updated here. | 1619 } |
| 1401 /* these need to be updated: | 1620 return super.findElementInScope(name); |
| 1402 'comment': comment, | |
| 1403 'type': new List.filled(1, type.toMap()), | |
| 1404 'annotations': annotations.map((a) => a.toMap()).toList() | |
| 1405 */ | |
| 1406 } | 1621 } |
| 1407 } | 1622 } |
| 1408 | 1623 |
| 1409 /// A class containing properties of a Dart method. | 1624 /// A class containing properties of a Dart method. |
| 1410 class Method extends Indexable { | 1625 class Method extends Indexable { |
| 1411 | 1626 |
| 1412 /// Parameters for this method. | 1627 /// Parameters for this method. |
| 1413 Map<String, Parameter> parameters; | 1628 Map<String, Parameter> parameters; |
| 1414 | 1629 |
| 1415 bool isStatic; | 1630 bool isStatic; |
| 1416 bool isAbstract; | 1631 bool isAbstract; |
| 1417 bool isConst; | 1632 bool isConst; |
| 1418 bool isConstructor; | 1633 bool isConstructor; |
| 1419 bool isGetter; | 1634 bool isGetter; |
| 1420 bool isSetter; | 1635 bool isSetter; |
| 1421 bool isOperator; | 1636 bool isOperator; |
| 1422 Type returnType; | 1637 Type returnType; |
| 1638 Method methodInheritedFrom; |
| 1423 | 1639 |
| 1424 /// Qualified name to state where the comment is inherited from. | 1640 /// Qualified name to state where the comment is inherited from. |
| 1425 String commentInheritedFrom = ""; | 1641 String commentInheritedFrom = ""; |
| 1426 | 1642 |
| 1427 /// List of the meta annotations on the method. | 1643 /// List of the meta annotations on the method. |
| 1428 List<Annotation> annotations; | 1644 List<Annotation> annotations; |
| 1429 | 1645 |
| 1430 Method(MethodMirror mirror) : super(mirror) { | 1646 Indexable owner; |
| 1647 |
| 1648 factory Method(MethodMirror mirror, Indexable owner, |
| 1649 [Method methodInheritedFrom]) { |
| 1650 var method = getDocgenObject(mirror, owner); |
| 1651 if (method is DummyMirror) { |
| 1652 method = new Method._(mirror, owner, methodInheritedFrom); |
| 1653 } |
| 1654 return method; |
| 1655 } |
| 1656 |
| 1657 Method._(MethodMirror mirror, this.owner, this.methodInheritedFrom) |
| 1658 : super(mirror) { |
| 1431 this.isStatic = mirror.isStatic; | 1659 this.isStatic = mirror.isStatic; |
| 1432 this.isAbstract = mirror.isAbstract; | 1660 this.isAbstract = mirror.isAbstract; |
| 1433 this.isConst = mirror.isConstConstructor; | 1661 this.isConst = mirror.isConstConstructor; |
| 1434 this.returnType = _createType(mirror.returnType); | 1662 this.returnType = new Type(mirror.returnType, _getOwningLibrary(owner)); |
| 1435 this.parameters = _createParameters(mirror.parameters); | 1663 this.parameters = _createParameters(mirror.parameters, owner); |
| 1436 this.annotations = _createAnnotations(mirror); | 1664 this.annotations = _createAnnotations(mirror, _getOwningLibrary(owner)); |
| 1437 this.isConstructor = mirror.isConstructor; | 1665 this.isConstructor = mirror.isConstructor; |
| 1438 this.isGetter = mirror.isGetter; | 1666 this.isGetter = mirror.isGetter; |
| 1439 this.isSetter = mirror.isSetter; | 1667 this.isSetter = mirror.isSetter; |
| 1440 this.isOperator = mirror.isOperator; | 1668 this.isOperator = mirror.isOperator; |
| 1441 } | 1669 } |
| 1442 | 1670 |
| 1671 String get packagePrefix => owner.packagePrefix; |
| 1672 |
| 1673 markdown.Node fixReferenceWithScope(String name) => fixReference(name); |
| 1674 |
| 1675 /// Look for the specified name starting with the current member, and |
| 1676 /// progressively working outward to the current library scope. |
| 1677 String findElementInScope(String name) { |
| 1678 var lookupFunc = Indexable.determineLookupFunc(name); |
| 1679 |
| 1680 var memberScope = lookupFunc(this.mirror, name); |
| 1681 if (memberScope != null) { |
| 1682 // do we check for a dummy mirror returned here and look up with an owner |
| 1683 // higher ooooor in getDocgenObject do we include more things in our |
| 1684 // lookup |
| 1685 var result = getDocgenObject(memberScope, owner); |
| 1686 if (result is DummyMirror && owner.owner != null |
| 1687 && owner.owner is! DummyMirror) { |
| 1688 var aresult = getDocgenObject(memberScope, owner.owner); |
| 1689 if (aresult is! DummyMirror) result = aresult; |
| 1690 } |
| 1691 if (result is DummyMirror) return packagePrefix + result.docName; |
| 1692 return result.packagePrefix + result.docName; |
| 1693 } |
| 1694 |
| 1695 if (owner != null) { |
| 1696 var result = owner.findElementInScope(name); |
| 1697 if (result != null) return result; |
| 1698 } |
| 1699 return super.findElementInScope(name); |
| 1700 } |
| 1701 |
| 1702 String get docName { |
| 1703 if ((mirror as MethodMirror).isConstructor) { |
| 1704 // We name constructors specially -- including the class name again and a |
| 1705 // "-" to separate the constructor from its name (if any). |
| 1706 return '${mirror.owner.simpleName.replaceAll(".", "_")}.' |
| 1707 '${mirror.owner.simpleName}-${mirror.simpleName}'; |
| 1708 } |
| 1709 return super.docName; |
| 1710 } |
| 1711 |
| 1443 /// Makes sure that the method with an inherited equivalent have comments. | 1712 /// Makes sure that the method with an inherited equivalent have comments. |
| 1444 void ensureCommentFor(Method inheritedMethod) { | 1713 void ensureCommentFor(Method inheritedMethod) { |
| 1445 if (comment.isNotEmpty) return; | 1714 if (comment.isNotEmpty) return; |
| 1446 comment = inheritedMethod._commentToHtml(mirror); | 1715 |
| 1716 comment = inheritedMethod._commentToHtml(this); |
| 1717 _unresolvedComment = inheritedMethod._unresolvedComment; |
| 1447 commentInheritedFrom = inheritedMethod.commentInheritedFrom == '' ? | 1718 commentInheritedFrom = inheritedMethod.commentInheritedFrom == '' ? |
| 1448 inheritedMethod.qualifiedName : inheritedMethod.commentInheritedFrom; | 1719 inheritedMethod.qualifiedName : inheritedMethod.commentInheritedFrom; |
| 1449 } | 1720 } |
| 1450 | 1721 |
| 1451 /// Generates a map describing the [Method] object. | 1722 /// Generates a map describing the [Method] object. |
| 1452 Map toMap() => { | 1723 Map toMap() => { |
| 1453 'name': name, | 1724 'name': name, |
| 1454 'qualifiedName': qualifiedName, | 1725 'qualifiedName': qualifiedName, |
| 1455 'comment': comment, | 1726 'comment': comment, |
| 1456 'commentFrom': commentInheritedFrom, | 1727 'commentFrom': (methodInheritedFrom != null && |
| 1728 commentInheritedFrom == methodInheritedFrom.docName ? '' |
| 1729 : commentInheritedFrom), |
| 1730 'inheritedFrom': (methodInheritedFrom == null? '' : |
| 1731 methodInheritedFrom.docName), |
| 1457 'static': isStatic.toString(), | 1732 'static': isStatic.toString(), |
| 1458 'abstract': isAbstract.toString(), | 1733 'abstract': isAbstract.toString(), |
| 1459 'constant': isConst.toString(), | 1734 'constant': isConst.toString(), |
| 1460 'return': new List.filled(1, returnType.toMap()), | 1735 'return': new List.filled(1, returnType.toMap()), |
| 1461 'parameters': recurseMap(parameters), | 1736 'parameters': recurseMap(parameters), |
| 1462 'annotations': annotations.map((a) => a.toMap()).toList() | 1737 'annotations': annotations.map((a) => a.toMap()).toList() |
| 1463 }; | 1738 }; |
| 1464 | 1739 |
| 1465 String get typeName => isConstructor ? 'constructor' : | 1740 String get typeName => isConstructor ? 'constructor' : |
| 1466 isGetter ? 'getter' : isSetter ? 'setter' : | 1741 isGetter ? 'getter' : isSetter ? 'setter' : |
| 1467 isOperator ? 'operator' : 'method'; | 1742 isOperator ? 'operator' : 'method'; |
| 1468 | 1743 |
| 1469 get comment { | 1744 get comment { |
| 1470 if (_comment != null) return _comment; | 1745 if (_comment != null) return _comment; |
| 1471 var owningClass = owningEntity; | 1746 if (owner is Class) { |
| 1472 if (owningClass is Class) { | 1747 (owner as Class).ensureComments(); |
| 1473 owningClass.ensureComments(); | |
| 1474 } | 1748 } |
| 1475 return super.comment; | 1749 var result = super.comment; |
| 1476 } | 1750 if (result == '' && methodInheritedFrom != null) { |
| 1477 } | 1751 // this should be NOT from the MIRROR, but from the COMMENT |
| 1752 _unresolvedComment = methodInheritedFrom._unresolvedComment; |
| 1478 | 1753 |
| 1479 class ExportedMethod extends Method implements Exported { | 1754 var linkResolver = (name) => fixReferenceWithScope(name); |
| 1480 Library _exportingLibrary; | 1755 comment = _unresolvedComment == null ? '' : |
| 1481 | 1756 markdown.markdownToHtml(_unresolvedComment.trim(), |
| 1482 ExportedMethod(MethodMirror originalMethod, Library this._exportingLibrary) : | 1757 linkResolver: linkResolver, inlineSyntaxes: _MARKDOWN_SYNTAXES); |
| 1483 super(originalMethod); | 1758 commentInheritedFrom = methodInheritedFrom.commentInheritedFrom; |
| 1484 | 1759 result = comment; |
| 1485 // TODO(efortuna): Refactor this code so the exported items can share this | 1760 //print('result was $comment'); |
| 1486 // behavior. | |
| 1487 String get fileName => path.join(_exportingLibrary.packageName, | |
| 1488 _exportingLibrary.mirror.qualifiedName + '.' + super.name); | |
| 1489 | |
| 1490 void updateExports(Map<String, Indexable> libraryExports) { | |
| 1491 // TODO(efortuna): if this class points to another exported class or type | |
| 1492 // of some sort, then that reference needs to be updated here. | |
| 1493 /* these need to be updated: | |
| 1494 'qualifiedName': qualifiedName, | |
| 1495 'comment': comment, | |
| 1496 'commentFrom': commentInheritedFrom, | |
| 1497 'return': new List.filled(1, returnType.toMap()), | |
| 1498 'parameters': recurseMap(parameters), | |
| 1499 'annotations': annotations.map((a) => a.toMap()).toList() | |
| 1500 */ | |
| 1501 } | |
| 1502 } | |
| 1503 | |
| 1504 | |
| 1505 | |
| 1506 /// A container to categorize methods into the following groups: setters, | |
| 1507 /// getters, constructors, operators, regular methods. | |
| 1508 class MethodGroup { | |
| 1509 Map<String, Method> setters = {}; | |
| 1510 Map<String, Method> getters = {}; | |
| 1511 Map<String, Method> constructors = {}; | |
| 1512 Map<String, Method> operators = {}; | |
| 1513 Map<String, Method> regularMethods = {}; | |
| 1514 | |
| 1515 Map<String, Exported> get exported { | |
| 1516 var exported = {}; | |
| 1517 for (Map<String, Method> group in [setters, getters, constructors, | |
| 1518 operators, regularMethods]) { | |
| 1519 exported = _filterMap(exported, group, | |
| 1520 (value) => value is ExportedMethod); | |
| 1521 } | 1761 } |
| 1522 return exported; | 1762 return result; |
| 1523 } | |
| 1524 | |
| 1525 /// The optional parameter [containingLibrary] is contains data for variables | |
| 1526 /// defined at the top level of a library (potentially for exporting | |
| 1527 /// purposes). | |
| 1528 void addMethod(MethodMirror mirror, [Library containingLibrary]) { | |
| 1529 var method; | |
| 1530 if (containingLibrary != null && mirror.owner.qualifiedName != | |
| 1531 containingLibrary.mirror.qualifiedName) { | |
| 1532 method = new ExportedMethod(mirror, containingLibrary); | |
| 1533 } else { | |
| 1534 method = new Method(mirror); | |
| 1535 } | |
| 1536 entityMap[docName(mirror)] = method; | |
| 1537 if (mirror.isSetter) { | |
| 1538 setters[mirror.simpleName] = method; | |
| 1539 } else if (mirror.isGetter) { | |
| 1540 getters[mirror.simpleName] = method; | |
| 1541 } else if (mirror.isConstructor) { | |
| 1542 constructors[mirror.simpleName] = method; | |
| 1543 } else if (mirror.isOperator) { | |
| 1544 operators[mirror.simpleName] = method; | |
| 1545 } else if (mirror.isRegularMethod) { | |
| 1546 regularMethods[mirror.simpleName] = method; | |
| 1547 } else { | |
| 1548 throw new ArgumentError('${mirror.simpleName} - no method type match'); | |
| 1549 } | |
| 1550 } | |
| 1551 | |
| 1552 void addInherited(Class parent) { | |
| 1553 setters.addAll(parent.inheritedMethods.setters); | |
| 1554 setters.addAll(_filterStatics(parent.methods.setters)); | |
| 1555 getters.addAll(parent.inheritedMethods.getters); | |
| 1556 getters.addAll(_filterStatics(parent.methods.getters)); | |
| 1557 operators.addAll(parent.inheritedMethods.operators); | |
| 1558 operators.addAll(_filterStatics(parent.methods.operators)); | |
| 1559 regularMethods.addAll(parent.inheritedMethods.regularMethods); | |
| 1560 regularMethods.addAll(_filterStatics(parent.methods.regularMethods)); | |
| 1561 } | |
| 1562 | |
| 1563 Map toMap() => { | |
| 1564 'setters': recurseMap(setters), | |
| 1565 'getters': recurseMap(getters), | |
| 1566 'constructors': recurseMap(constructors), | |
| 1567 'operators': recurseMap(operators), | |
| 1568 'methods': recurseMap(regularMethods) | |
| 1569 }; | |
| 1570 | |
| 1571 Method operator [](String qualifiedName) { | |
| 1572 if (setters.containsKey(qualifiedName)) return setters[qualifiedName]; | |
| 1573 if (getters.containsKey(qualifiedName)) return getters[qualifiedName]; | |
| 1574 if (operators.containsKey(qualifiedName)) return operators[qualifiedName]; | |
| 1575 if (regularMethods.containsKey(qualifiedName)) { | |
| 1576 return regularMethods[qualifiedName]; | |
| 1577 } | |
| 1578 return null; | |
| 1579 } | |
| 1580 | |
| 1581 void forEach(void f(String key, Method value)) { | |
| 1582 setters.forEach(f); | |
| 1583 getters.forEach(f); | |
| 1584 operators.forEach(f); | |
| 1585 regularMethods.forEach(f); | |
| 1586 } | 1763 } |
| 1587 } | 1764 } |
| 1588 | 1765 |
| 1589 /// A class containing properties of a Dart method/function parameter. | 1766 /// A class containing properties of a Dart method/function parameter. |
| 1590 class Parameter { | 1767 class Parameter extends MirrorBased { |
| 1591 | 1768 |
| 1769 ParameterMirror mirror; |
| 1592 String name; | 1770 String name; |
| 1593 bool isOptional; | 1771 bool isOptional; |
| 1594 bool isNamed; | 1772 bool isNamed; |
| 1595 bool hasDefaultValue; | 1773 bool hasDefaultValue; |
| 1596 Type type; | 1774 Type type; |
| 1597 String defaultValue; | 1775 String defaultValue; |
| 1598 | 1776 |
| 1599 /// List of the meta annotations on the parameter. | 1777 /// List of the meta annotations on the parameter. |
| 1600 List<Annotation> annotations; | 1778 List<Annotation> annotations; |
| 1601 | 1779 |
| 1602 Parameter(this.name, this.isOptional, this.isNamed, this.hasDefaultValue, | 1780 Parameter(this.mirror, [Indexable owner]) { |
| 1603 this.type, this.defaultValue, this.annotations); | 1781 name = mirror.simpleName; |
| 1782 isOptional = mirror.isOptional; |
| 1783 isNamed = mirror.isNamed; |
| 1784 hasDefaultValue = mirror.hasDefaultValue; |
| 1785 defaultValue = mirror.defaultValue; |
| 1786 type = new Type(mirror.type, owner); |
| 1787 annotations = _createAnnotations(mirror, this); |
| 1788 } |
| 1604 | 1789 |
| 1605 /// Generates a map describing the [Parameter] object. | 1790 /// Generates a map describing the [Parameter] object. |
| 1606 Map toMap() => { | 1791 Map toMap() => { |
| 1607 'name': name, | 1792 'name': name, |
| 1608 'optional': isOptional.toString(), | 1793 'optional': isOptional.toString(), |
| 1609 'named': isNamed.toString(), | 1794 'named': isNamed.toString(), |
| 1610 'default': hasDefaultValue.toString(), | 1795 'default': hasDefaultValue.toString(), |
| 1611 'type': new List.filled(1, type.toMap()), | 1796 'type': new List.filled(1, type.toMap()), |
| 1612 'value': defaultValue, | 1797 'value': defaultValue, |
| 1613 'annotations': annotations.map((a) => a.toMap()).toList() | 1798 'annotations': annotations.map((a) => a.toMap()).toList() |
| 1614 }; | 1799 }; |
| 1615 } | 1800 } |
| 1616 | 1801 |
| 1617 /// A class containing properties of a Generic. | 1802 /// A Docgen wrapper around the dart2js mirror for a generic type. |
| 1618 class Generic { | 1803 class Generic extends MirrorBased { |
| 1619 String name; | 1804 TypeVariableMirror mirror; |
| 1620 String type; | 1805 Generic(this.mirror); |
| 1621 | |
| 1622 Generic(this.name, this.type); | |
| 1623 | |
| 1624 Map toMap() => { | 1806 Map toMap() => { |
| 1625 'name': name, | 1807 'name': mirror.toString(), |
| 1626 'type': type | 1808 'type': mirror.upperBound.qualifiedName |
| 1627 }; | 1809 }; |
| 1628 } | 1810 } |
| 1629 | 1811 |
| 1630 /// Holds the name of a return type, and its generic type parameters. | 1812 /// Holds the name of a return type, and its generic type parameters. |
| 1631 /// | 1813 /// |
| 1632 /// Return types are of a form [outer]<[inner]>. | 1814 /// Return types are of a form [outer]<[inner]>. |
| 1633 /// If there is no [inner] part, [inner] will be an empty list. | 1815 /// If there is no [inner] part, [inner] will be an empty list. |
| 1634 /// | 1816 /// |
| 1635 /// For example: | 1817 /// For example: |
| 1636 /// int size() | 1818 /// int size() |
| (...skipping 11 matching lines...) Expand all Loading... |
| 1648 /// Map<String, List<int>> | 1830 /// Map<String, List<int>> |
| 1649 /// "return" : | 1831 /// "return" : |
| 1650 /// - "outer" : "dart-core.Map" | 1832 /// - "outer" : "dart-core.Map" |
| 1651 /// "inner" : | 1833 /// "inner" : |
| 1652 /// - "outer" : "dart-core.String" | 1834 /// - "outer" : "dart-core.String" |
| 1653 /// "inner" : | 1835 /// "inner" : |
| 1654 /// - "outer" : "dart-core.List" | 1836 /// - "outer" : "dart-core.List" |
| 1655 /// "inner" : | 1837 /// "inner" : |
| 1656 /// - "outer" : "dart-core.int" | 1838 /// - "outer" : "dart-core.int" |
| 1657 /// "inner" : | 1839 /// "inner" : |
| 1658 class Type { | 1840 class Type extends MirrorBased { |
| 1659 String outer; | 1841 TypeMirror mirror; |
| 1660 List<Type> inner; | 1842 MirrorBased owner; |
| 1661 | 1843 |
| 1662 Type(this.outer, this.inner); | 1844 factory Type(TypeMirror mirror, [MirrorBased owner]) { |
| 1845 return new Type._(mirror, owner); |
| 1846 } |
| 1663 | 1847 |
| 1664 Map toMap() => { | 1848 Type._(this.mirror, this.owner); |
| 1665 'outer': outer, | 1849 |
| 1666 'inner': inner.map((e) => e.toMap()).toList() | 1850 /// Returns a list of [Type] objects constructed from TypeMirrors. |
| 1667 }; | 1851 List<Type> _createTypeGenerics(TypeMirror mirror) { |
| 1852 if (mirror is ClassMirror && !mirror.isTypedef) { |
| 1853 var innerList = []; |
| 1854 mirror.typeArguments.forEach((e) { |
| 1855 innerList.add(new Type(e, owner)); |
| 1856 }); |
| 1857 return innerList; |
| 1858 } |
| 1859 return []; |
| 1860 } |
| 1861 |
| 1862 Map toMap() { |
| 1863 // We may encounter types whose corresponding library has not been |
| 1864 // processed yet, so look up the owner at the last moment. |
| 1865 var result = getDocgenObject(mirror, owner); |
| 1866 return { |
| 1867 'outer': result.docName, |
| 1868 'inner': _createTypeGenerics(mirror).map((e) => e.toMap()).toList(), |
| 1869 }; |
| 1870 } |
| 1668 } | 1871 } |
| 1669 | 1872 |
| 1670 /// Holds the name of the annotation, and its parameters. | 1873 /// Holds the name of the annotation, and its parameters. |
| 1671 class Annotation { | 1874 class Annotation extends MirrorBased { |
| 1672 String qualifiedName; | |
| 1673 List<String> parameters; | 1875 List<String> parameters; |
| 1876 /// The class of this annotation. |
| 1877 ClassMirror mirror; |
| 1674 | 1878 |
| 1675 Annotation(this.qualifiedName, this.parameters); | 1879 Annotation(InstanceMirror originalMirror, MirrorBased annotationOwner) { |
| 1880 mirror = originalMirror.type; |
| 1881 parameters = originalMirror.type.variables.values |
| 1882 .where((e) => e.isFinal) |
| 1883 .map((e) => originalMirror.getField(e.simpleName).reflectee) |
| 1884 .where((e) => e != null) |
| 1885 .toList(); |
| 1886 owner = annotationOwner; |
| 1887 } |
| 1676 | 1888 |
| 1677 Map toMap() => { | 1889 Map toMap() => { |
| 1678 'name': qualifiedName, | 1890 'name': getDocgenObject(mirror, owner).docName, |
| 1679 'parameters': parameters | 1891 'parameters': parameters |
| 1680 }; | 1892 }; |
| 1681 } | 1893 } |
| 1682 | |
| 1683 /// Given a mirror, returns its qualified name, but following the conventions | |
| 1684 /// we're using in Dartdoc, which is that library names with dots in them | |
| 1685 /// have them replaced with hyphens. | |
| 1686 String docName(DeclarationMirror m) { | |
| 1687 if (m is LibraryMirror) { | |
| 1688 return m.qualifiedName.replaceAll('.','-'); | |
| 1689 } | |
| 1690 var owner = m.owner; | |
| 1691 if (owner == null) return m.qualifiedName; | |
| 1692 var simpleName = m.simpleName; | |
| 1693 if (m is MethodMirror && m.isConstructor) { | |
| 1694 // We name constructors specially -- including the class name again and a | |
| 1695 // "-" to separate the constructor from its name (if any). | |
| 1696 simpleName = '${owner.simpleName}-$simpleName'; | |
| 1697 } | |
| 1698 return docName(owner) + '.' + simpleName; | |
| 1699 } | |
| 1700 | |
| 1701 /// Remove statics from the map of inherited items before adding them. | |
| 1702 Map _filterStatics(Map items) { | |
| 1703 var result = {}; | |
| 1704 items.forEach((name, item) { | |
| 1705 if (!item.isStatic) { | |
| 1706 result[name] = item; | |
| 1707 } | |
| 1708 }); | |
| 1709 return result; | |
| 1710 } | |
| OLD | NEW |