| Index: pkg/docgen/lib/docgen.dart
|
| diff --git a/pkg/docgen/lib/docgen.dart b/pkg/docgen/lib/docgen.dart
|
| index d75b60f6910434507e801f508b942d694a4d4623..febf458a79bcdd70d72a45449b0225144baa7e71 100644
|
| --- a/pkg/docgen/lib/docgen.dart
|
| +++ b/pkg/docgen/lib/docgen.dart
|
| @@ -34,251 +34,162 @@ import '../../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors_util.
|
| import '../../../sdk/lib/_internal/compiler/implementation/source_file_provider.dart';
|
| import '../../../sdk/lib/_internal/libraries.dart';
|
|
|
| -var logger = new Logger('Docgen');
|
| +const _DEFAULT_OUTPUT_DIRECTORY = 'docs';
|
|
|
| -const DEFAULT_OUTPUT_DIRECTORY = 'docs';
|
| -
|
| -var _outputDirectory;
|
| -
|
| -const String USAGE = 'Usage: dart docgen.dart [OPTIONS] fooDir/barFile';
|
| -
|
| -
|
| -List<String> skippedAnnotations = const [
|
| +/// Annotations that we do not display in the viewer.
|
| +const List<String> _SKIPPED_ANNOTATIONS = const [
|
| 'metadata.DocsEditable', '_js_helper.JSName', '_js_helper.Creates',
|
| '_js_helper.Returns'];
|
|
|
| -/// Set of libraries declared in the SDK, so libraries that can be accessed
|
| -/// when running dart by default.
|
| -Iterable<LibraryMirror> _sdkLibraries;
|
| -
|
| -/// The dart:core library, which contains all types that are always available
|
| -/// without import.
|
| -LibraryMirror _coreLibrary;
|
| -
|
| /// Support for [:foo:]-style code comments to the markdown parser.
|
| -List<markdown.InlineSyntax> markdownSyntaxes =
|
| +List<markdown.InlineSyntax> _MARKDOWN_SYNTAXES =
|
| [new markdown.CodeSyntax(r'\[:\s?((?:.|\n)*?)\s?:\]')];
|
|
|
| +// TODO(efortuna): The use of this field is odd (this is based on how it was
|
| +// originally used. Try to cleanup.
|
| /// Index of all indexable items. This also ensures that no class is
|
| /// created more than once.
|
| Map<String, Indexable> entityMap = new Map<String, Indexable>();
|
|
|
| -/// This is set from the command line arguments flag --include-private
|
| -bool _includePrivate = false;
|
| -
|
| -/// This is set from the command line flag --include-dependent-packages
|
| -bool _includeDependentPackages = false;
|
| -
|
| -/// Library names to explicitly exclude.
|
| +/// Index of all the dart2js mirrors examined to corresponding MirrorBased
|
| +/// docgen objects.
|
| ///
|
| -/// Set from the command line option
|
| -/// --exclude-lib.
|
| -List<String> _excluded;
|
| +/// Used for lookup because of the dart2js mirrors exports
|
| +/// issue. The second level map is indexed by owner docName for faster lookup.
|
| +/// Why two levels of lookup? Speed, man. Speed.
|
| +Map<String, Map<String, Set<MirrorBased>>> mirrorToDocgen =
|
| + new Map<String, Map<String, Set<MirrorBased>>>();
|
|
|
| -// TODO(janicejl): Make MDN content generic or pluggable. Maybe move
|
| -// MDN-specific code to its own library that is imported into the default impl?
|
| -/// Map of all the comments for dom elements from MDN.
|
| -Map _mdn;
|
| -
|
| -/// Docgen constructor initializes the link resolver for markdown parsing.
|
| -/// Also initializes the command line arguments.
|
| +/// Given a Dart2jsMirror, find the corresponding Docgen [MirrorBased] object.
|
| ///
|
| -/// [packageRoot] is the packages directory of the directory being analyzed.
|
| -/// If [includeSdk] is `true`, then any SDK libraries explicitly imported will
|
| -/// also be documented.
|
| -/// If [parseSdk] is `true`, then all Dart SDK libraries will be documented.
|
| -/// This option is useful when only the SDK libraries are needed.
|
| -///
|
| -/// Returned Future completes with true if document generation is successful.
|
| -Future<bool> docgen(List<String> files, {String packageRoot,
|
| - bool outputToYaml: true, bool includePrivate: false, bool includeSdk: false,
|
| - bool parseSdk: false, bool append: false, String introduction: '',
|
| - out: DEFAULT_OUTPUT_DIRECTORY, List<String> excludeLibraries,
|
| - bool includeDependentPackages}) {
|
| - _excluded = excludeLibraries;
|
| - _includePrivate = includePrivate;
|
| - _outputDirectory = out;
|
| - _includeDependentPackages = includeDependentPackages;
|
| - if (!append) {
|
| - var dir = new Directory(_outputDirectory);
|
| - if (dir.existsSync()) dir.deleteSync(recursive: true);
|
| - }
|
| -
|
| - if (packageRoot == null && !parseSdk) {
|
| - var type = FileSystemEntity.typeSync(files.first);
|
| - if (type == FileSystemEntityType.DIRECTORY) {
|
| - packageRoot = _findPackageRoot(files.first);
|
| - } else if (type == FileSystemEntityType.FILE) {
|
| - logger.warning('WARNING: No package root defined. If Docgen fails, try '
|
| - 'again by setting the --package-root option.');
|
| +/// We have this global lookup function to avoid re-implementing looking up the
|
| +/// scoping rules for comment resolution here (it is currently done in mirrors).
|
| +/// If no corresponding MirrorBased object is found, we return a [DummyMirror]
|
| +/// that simply returns the original mirror's qualifiedName while behaving like
|
| +/// a MirrorBased object.
|
| +MirrorBased getDocgenObject(DeclarationMirror mirror, [Indexable owner]) {
|
| + Map<String, Set<MirrorBased>> docgenObj =
|
| + mirrorToDocgen[mirror.qualifiedName];
|
| + if (docgenObj == null) {
|
| + return new DummyMirror(mirror, owner);
|
| + }
|
| +
|
| + Set<MirrorBased> results = new Set<MirrorBased>();
|
| + var setToExamine = new Set();
|
| + if (owner != null) {
|
| + var firstSet = docgenObj[owner.docName];
|
| + if (firstSet != null) setToExamine.addAll(firstSet);
|
| + if (Indexable._coreLibrary != null &&
|
| + docgenObj[Indexable._coreLibrary.docName] != null) {
|
| + setToExamine.addAll(docgenObj[Indexable._coreLibrary.docName]);
|
| + }
|
| + } else {
|
| + for (var value in docgenObj.values) {
|
| + setToExamine.addAll(value);
|
| }
|
| - }
|
| - logger.info('Package Root: ${packageRoot}');
|
| - if (_includeDependentPackages) {
|
| - files.addAll(allDependentPackageDirs(files.first));
|
| - }
|
| - var requestedLibraries = _listLibraries(files);
|
| - var allLibraries = []..addAll(requestedLibraries);
|
| - if (includeSdk) {
|
| - allLibraries.addAll(_listSdk());
|
| }
|
|
|
| - return getMirrorSystem(allLibraries, packageRoot: packageRoot,
|
| - parseSdk: parseSdk)
|
| - .then((MirrorSystem mirrorSystem) {
|
| - if (mirrorSystem.libraries.isEmpty) {
|
| - throw new StateError('No library mirrors were created.');
|
| - }
|
| - var availableLibraries = mirrorSystem.libraries.values.where(
|
| - (each) => each.uri.scheme == 'file');
|
| - _sdkLibraries = mirrorSystem.libraries.values.where(
|
| - (each) => each.uri.scheme == 'dart');
|
| - _coreLibrary = _sdkLibraries.singleWhere((lib) =>
|
| - lib.uri.toString().startsWith('dart:core'));
|
| - var availableLibrariesByPath = new Map.fromIterables(
|
| - availableLibraries.map((each) => each.uri),
|
| - availableLibraries);
|
| - var librariesToDocument = requestedLibraries.map(
|
| - (each) => availableLibrariesByPath.putIfAbsent(each,
|
| - () => throw "Missing library $each")).toList();
|
| - librariesToDocument.addAll((includeSdk || parseSdk) ? _sdkLibraries : []);
|
| - librariesToDocument.removeWhere((x) => _excluded.contains(x.simpleName));
|
| - _documentLibraries(librariesToDocument, includeSdk: includeSdk,
|
| - outputToYaml: outputToYaml, append: append, parseSdk: parseSdk,
|
| - introduction: introduction);
|
| - return true;
|
| - });
|
| -}
|
| + for(MirrorBased mirrorBased in setToExamine) {
|
| + // This check makes me sad, but Dart doesn't seem to be dynamic enough to
|
| + // write this another way. Dart2js and the editor didn't like the Type
|
| + // lookup in a map.
|
| + if (mirrorBased.mirror.qualifiedName == mirror.qualifiedName &&
|
| + ((mirror is ClassMirror && mirrorBased is Class)
|
| + || (mirror is LibraryMirror && mirrorBased is Library)
|
| + || (mirror is MethodMirror && mirrorBased is Method)
|
| + || (mirror is VariableMirror && mirrorBased is Variable)
|
| + || (mirror is TypedefMirror && mirrorBased is Typedef)
|
| + || (mirror is TypeMirror && mirrorBased is Type)
|
| + || (mirror is InstanceMirror && mirrorBased is Annotation))) {
|
| + results.add(mirrorBased);
|
| + }
|
| + }
|
|
|
| -/// All of the directories for our dependent packages
|
| -List<String> allDependentPackageDirs(String packageDirectory) {
|
| - var dependentsJson = Process.runSync('pub', ['list-package-dirs'],
|
| - workingDirectory: packageDirectory, runInShell: true);
|
| - if (dependentsJson.exitCode != 0) {
|
| - print(dependentsJson.stderr);
|
| + if (results.length > 0) {
|
| + return results.first; // This might occur if we didn't specify an "owner."
|
| }
|
| - var dependents = JSON.decode(dependentsJson.stdout)['packages'];
|
| - return dependents.values.toList();
|
| + return new DummyMirror(mirror, owner);
|
| }
|
|
|
| -/// For a library's [mirror], determine the name of the package (if any) we
|
| -/// believe it came from (because of its file URI).
|
| -///
|
| -/// If [library] is specified, we set the packageName field. If no package could
|
| -/// be determined, we return an empty string.
|
| -String _findPackage(LibraryMirror mirror, [Library library]) {
|
| - if (mirror == null) return '';
|
| - if (library == null) {
|
| - library = entityMap[docName(mirror)];
|
| - }
|
| - if (library != null) {
|
| - if (library.hasBeenCheckedForPackage) return library.packageName;
|
| - library.hasBeenCheckedForPackage = true;
|
| - }
|
| - if (mirror.uri.scheme != 'file') return '';
|
| - var filePath = mirror.uri.toFilePath();
|
| - // We assume that we are documenting only libraries under package/lib
|
| - var rootdir = path.dirname((path.dirname(filePath)));
|
| - var pubspec = path.join(rootdir, 'pubspec.yaml');
|
| - var packageName = _packageName(pubspec);
|
| - if (library != null) {
|
| - library.packageName = packageName;
|
| - // Associate the package readme with all the libraries. This is a bit
|
| - // wasteful, but easier than trying to figure out which partial match
|
| - // is best.
|
| - library.packageIntro = _packageIntro(rootdir);
|
| +/// For types that we do not explicitly create or have not yet created in our
|
| +/// entity map (like core types).
|
| +class DummyMirror implements MirrorBased {
|
| + DeclarationMirror mirror;
|
| + /// The library that contains this element, if any. Used as a hint to help
|
| + /// determine which object we're referring to when looking up this mirror in
|
| + /// our map.
|
| + Indexable owner;
|
| + DummyMirror(this.mirror, [this.owner]);
|
| +
|
| + String get docName {
|
| + if (mirror == null) return '';
|
| + if (mirror is LibraryMirror) {
|
| + return mirror.qualifiedName.replaceAll('.','-');
|
| + }
|
| + var mirrorOwner = mirror.owner;
|
| + if (mirrorOwner == null) return mirror.qualifiedName;
|
| + var simpleName = mirror.simpleName;
|
| + if (mirror is MethodMirror && (mirror as MethodMirror).isConstructor) {
|
| + // We name constructors specially -- including the class name again and a
|
| + // "-" to separate the constructor from its name (if any).
|
| + simpleName = '${mirrorOwner.simpleName}-$simpleName';
|
| + }
|
| + return getDocgenObject(mirrorOwner, owner).docName + '.' + simpleName;
|
| }
|
| - return packageName;
|
| -}
|
| + List<Annotation> _createAnnotations(DeclarationMirror mirror,
|
| + MirrorBased owner) => null;
|
|
|
| -String _packageIntro(packageDir) {
|
| - var dir = new Directory(packageDir);
|
| - var files = dir.listSync();
|
| - var readmes = files.where((FileSystemEntity each) => (each is File &&
|
| - each.path.substring(packageDir.length + 1, each.path.length)
|
| - .startsWith('README'))).toList();
|
| - if (readmes.isEmpty) return '';
|
| - // If there are multiples, pick the shortest name.
|
| - readmes.sort((a, b) => a.path.length.compareTo(b.path.length));
|
| - var readme = readmes.first;
|
| - var linkResolver = (name) => fixReference(name, null, null, null);
|
| - var contents = markdown.markdownToHtml(readme
|
| - .readAsStringSync(), linkResolver: linkResolver,
|
| - inlineSyntaxes: markdownSyntaxes);
|
| - return contents;
|
| + bool get isPrivate => mirror == null? false : mirror.isPrivate;
|
| }
|
|
|
| -List<Uri> _listLibraries(List<String> args) {
|
| - var libraries = new List<Uri>();
|
| - for (var arg in args) {
|
| - var type = FileSystemEntity.typeSync(arg);
|
| +abstract class MirrorBased {
|
| + DeclarationMirror get mirror;
|
| + MirrorBased owner;
|
|
|
| - if (type == FileSystemEntityType.FILE) {
|
| - if (arg.endsWith('.dart')) {
|
| - libraries.add(new Uri.file(path.absolute(arg)));
|
| - logger.info('Added to libraries: ${libraries.last}');
|
| - }
|
| - } else {
|
| - libraries.addAll(_listDartFromDir(arg));
|
| - }
|
| - }
|
| - return libraries;
|
| -}
|
| + /// Returns this object's qualified name, but following the conventions
|
| + /// we're using in Dartdoc, which is that library names with dots in them
|
| + /// have them replaced with hyphens.
|
| + String get docName => owner.docName + '.' + mirror.simpleName;
|
|
|
| -List<Uri> _listDartFromDir(String args) {
|
| - var libraries = [];
|
| - // To avoid anaylzing package files twice, only files with paths not
|
| - // containing '/packages' will be added. The only exception is if the file to
|
| - // analyze already has a '/package' in its path.
|
| - var files = listDir(args, recursive: true).where((f) => f.endsWith('.dart') &&
|
| - (!f.contains('${path.separator}packages') ||
|
| - args.contains('${path.separator}packages'))).toList();
|
| -
|
| - files.forEach((String f) {
|
| - // Only include libraries at the top level of "lib"
|
| - if (path.basename(path.dirname(f)) == 'lib') {
|
| - // Only add the file if it does not contain 'part of'
|
| - // TODO(janicejl): Remove when Issue(12406) is resolved.
|
| - var contents = new File(f).readAsStringSync();
|
| - if (!(contents.contains(new RegExp('\npart of ')) ||
|
| - contents.startsWith(new RegExp('part of ')))) {
|
| - libraries.add(new Uri.file(path.normalize(path.absolute(f))));
|
| - logger.info('Added to libraries: $f');
|
| + /// Returns a list of meta annotations assocated with a mirror.
|
| + List<Annotation> _createAnnotations(DeclarationMirror mirror,
|
| + MirrorBased owner) {
|
| + var annotationMirrors = mirror.metadata.where((e) =>
|
| + e is dart2js.Dart2JsConstructedConstantMirror);
|
| + var annotations = [];
|
| + annotationMirrors.forEach((annotation) {
|
| + var docgenAnnotation = new Annotation(annotation, owner);
|
| + if (!_SKIPPED_ANNOTATIONS.contains(
|
| + docgenAnnotation.mirror.qualifiedName)) {
|
| + annotations.add(docgenAnnotation);
|
| }
|
| - }
|
| - });
|
| - return libraries;
|
| -}
|
| -
|
| -String _findPackageRoot(String directory) {
|
| - var files = listDir(directory, recursive: true);
|
| - // Return '' means that there was no pubspec.yaml and therefor no packageRoot.
|
| - String packageRoot = files.firstWhere((f) =>
|
| - f.endsWith('${path.separator}pubspec.yaml'), orElse: () => '');
|
| - if (packageRoot != '') {
|
| - packageRoot = path.join(path.dirname(packageRoot), 'packages');
|
| + });
|
| + return annotations;
|
| }
|
| - return packageRoot;
|
| -}
|
|
|
| -/// Read a pubspec and return the library name.
|
| -String _packageName(String pubspecName) {
|
| - File pubspec = new File(pubspecName);
|
| - if (!pubspec.existsSync()) return '';
|
| - var contents = pubspec.readAsStringSync();
|
| - var spec = loadYaml(contents);
|
| - return spec["name"];
|
| + bool get isPrivate => false;
|
| }
|
|
|
| -List<Uri> _listSdk() {
|
| - var sdk = new List<Uri>();
|
| - LIBRARIES.forEach((String name, LibraryInfo info) {
|
| - if (info.documented) {
|
| - sdk.add(Uri.parse('dart:$name'));
|
| - logger.info('Add to SDK: ${sdk.last}');
|
| - }
|
| - });
|
| - return sdk;
|
| +/// Docgen constructor initializes the link resolver for markdown parsing.
|
| +/// Also initializes the command line arguments.
|
| +///
|
| +/// [packageRoot] is the packages directory of the directory being analyzed.
|
| +/// If [includeSdk] is `true`, then any SDK libraries explicitly imported will
|
| +/// also be documented.
|
| +/// If [parseSdk] is `true`, then all Dart SDK libraries will be documented.
|
| +/// This option is useful when only the SDK libraries are needed.
|
| +///
|
| +/// Returned Future completes with true if document generation is successful.
|
| +Future<bool> docgen(List<String> files, {String packageRoot,
|
| + bool outputToYaml: true, bool includePrivate: false, bool includeSdk: false,
|
| + bool parseSdk: false, bool append: false, String introduction: '',
|
| + out: _DEFAULT_OUTPUT_DIRECTORY, List<String> excludeLibraries : const [],
|
| + bool includeDependentPackages: false}) {
|
| + return _Generator.generateDocumentation(files, packageRoot: packageRoot,
|
| + outputToYaml: outputToYaml, includePrivate: includePrivate,
|
| + includeSdk: includeSdk, parseSdk: parseSdk, append: append,
|
| + introduction: introduction, out: out, excludeLibraries: excludeLibraries,
|
| + includeDependentPackages: includeDependentPackages);
|
| }
|
|
|
| /// Analyzes set of libraries by getting a mirror system and triggers the
|
| @@ -288,437 +199,472 @@ Future<MirrorSystem> getMirrorSystem(List<Uri> libraries,
|
| if (libraries.isEmpty) throw new StateError('No Libraries.');
|
| // Finds the root of SDK library based off the location of docgen.
|
|
|
| - var root = findRootDirectory();
|
| + var root = _Generator._rootDirectory;
|
| var sdkRoot = path.normalize(path.absolute(path.join(root, 'sdk')));
|
| - logger.info('SDK Root: ${sdkRoot}');
|
| - return _analyzeLibraries(libraries, sdkRoot, packageRoot: packageRoot);
|
| + _Generator.logger.info('SDK Root: ${sdkRoot}');
|
| + return _Generator._analyzeLibraries(libraries, sdkRoot,
|
| + packageRoot: packageRoot);
|
| }
|
|
|
| -String findRootDirectory() {
|
| - var scriptDir = path.absolute(path.dirname(Platform.script.toFilePath()));
|
| - var root = scriptDir;
|
| - while(path.basename(root) != 'dart') {
|
| - root = path.dirname(root);
|
| - }
|
| - return root;
|
| -}
|
| +class _Generator {
|
| + static var _outputDirectory;
|
|
|
| -/// Analyzes set of libraries and provides a mirror system which can be used
|
| -/// for static inspection of the source code.
|
| -Future<MirrorSystem> _analyzeLibraries(List<Uri> libraries,
|
| - String libraryRoot, {String packageRoot}) {
|
| - SourceFileProvider provider = new CompilerSourceFileProvider();
|
| - api.DiagnosticHandler diagnosticHandler =
|
| - (new FormattingDiagnosticHandler(provider)
|
| - ..showHints = false
|
| - ..showWarnings = false)
|
| - .diagnosticHandler;
|
| - Uri libraryUri = new Uri.file(appendSlash(libraryRoot));
|
| - Uri packageUri = null;
|
| - if (packageRoot != null) {
|
| - packageUri = new Uri.file(appendSlash(packageRoot));
|
| - }
|
| - return dart2js.analyze(libraries, libraryUri, packageUri,
|
| - provider.readStringFromUri, diagnosticHandler,
|
| - ['--preserve-comments', '--categories=Client,Server'])
|
| - ..catchError((error) {
|
| - logger.severe('Error: Failed to create mirror system. ');
|
| - // TODO(janicejl): Use the stack trace package when bug is resolved.
|
| - // Currently, a string is thrown when it fails to create a mirror
|
| - // system, and it is not possible to use the stack trace. BUG(#11622)
|
| - // To avoid printing the stack trace.
|
| - exit(1);
|
| - });
|
| -}
|
| + /// This is set from the command line arguments flag --include-private
|
| + static bool _includePrivate = false;
|
|
|
| -/// Creates documentation for filtered libraries.
|
| -void _documentLibraries(List<LibraryMirror> libs, {bool includeSdk: false,
|
| - bool outputToYaml: true, bool append: false, bool parseSdk: false,
|
| - String introduction: ''}) {
|
| - libs.forEach((lib) {
|
| - // Files belonging to the SDK have a uri that begins with 'dart:'.
|
| - if (includeSdk || !lib.uri.toString().startsWith('dart:')) {
|
| - var library = generateLibrary(lib);
|
| - entityMap[library.name] = library;
|
| - }
|
| - });
|
| - // After everything is created, do a pass through all classes to make sure no
|
| - // intermediate classes created by mixins are included, all the links to
|
| - // exported members point to the new library.
|
| - entityMap.values.where((e) => e is Class).forEach(
|
| - (c) => c.updateLinksAndRemoveIntermediaryClasses());
|
| - // Everything is a subclass of Object, therefore empty the list to avoid a
|
| - // giant list of subclasses to be printed out.
|
| - if (includeSdk) (entityMap['dart-core.Object'] as Class).subclasses.clear();
|
| -
|
| - var filteredEntities = entityMap.values.where(_isVisible);
|
| -
|
| - // Outputs a JSON file with all libraries and their preview comments.
|
| - // This will help the viewer know what libraries are available to read in.
|
| - var libraryMap;
|
| - var linkResolver = (name) => fixReference(name, null, null, null);
|
| - if (append) {
|
| - var docsDir = listDir(_outputDirectory);
|
| - if (!docsDir.contains('$_outputDirectory/library_list.json')) {
|
| - throw new StateError('No library_list.json');
|
| - }
|
| - libraryMap =
|
| - JSON.decode(new File(
|
| - '$_outputDirectory/library_list.json').readAsStringSync());
|
| - libraryMap['libraries'].addAll(filteredEntities
|
| - .where((e) => e is Library)
|
| - .map((e) => e.previewMap));
|
| - if (introduction.isNotEmpty) {
|
| - var intro = libraryMap['introduction'];
|
| - if (intro.isNotEmpty) intro += '<br/><br/>';
|
| - intro += markdown.markdownToHtml(
|
| - new File(introduction).readAsStringSync(),
|
| - linkResolver: linkResolver, inlineSyntaxes: markdownSyntaxes);
|
| - libraryMap['introduction'] = intro;
|
| - }
|
| - outputToYaml = libraryMap['filetype'] == 'yaml';
|
| - } else {
|
| - libraryMap = {
|
| - 'libraries' : filteredEntities.where((e) =>
|
| - e is Library).map((e) => e.previewMap).toList(),
|
| - 'introduction' : introduction == '' ?
|
| - '' : markdown.markdownToHtml(new File(introduction)
|
| - .readAsStringSync(), linkResolver: linkResolver,
|
| - inlineSyntaxes: markdownSyntaxes),
|
| - 'filetype' : outputToYaml ? 'yaml' : 'json'
|
| - };
|
| - }
|
| - _writeToFile(JSON.encode(libraryMap), 'library_list.json');
|
| -
|
| - // Output libraries and classes to file after all information is generated.
|
| - filteredEntities.where((e) => e is Class || e is Library).forEach((output) {
|
| - _writeIndexableToFile(output, outputToYaml);
|
| - });
|
| -
|
| - // Outputs all the qualified names documented with their type.
|
| - // This will help generate search results.
|
| - _writeToFile(filteredEntities.map((e) =>
|
| - '${e.qualifiedName} ${e.typeName}').join('\n') + '\n',
|
| - 'index.txt', append: append);
|
| - var index = new Map.fromIterables(
|
| - filteredEntities.map((e) => e.qualifiedName),
|
| - filteredEntities.map((e) => e.typeName));
|
| - if (append) {
|
| - var previousIndex =
|
| - JSON.decode(new File('$_outputDirectory/index.json').readAsStringSync());
|
| - index.addAll(previousIndex);
|
| - }
|
| - _writeToFile(JSON.encode(index), 'index.json');
|
| -}
|
| + /// Library names to explicitly exclude.
|
| + ///
|
| + /// Set from the command line option
|
| + /// --exclude-lib.
|
| + static List<String> _excluded;
|
|
|
| -Library generateLibrary(dart2js.Dart2JsLibraryMirror library) {
|
| - var result = new Library(library);
|
| - _findPackage(library, result);
|
| - logger.fine('Generated library for ${result.name}');
|
| - return result;
|
| -}
|
| + static Logger logger = new Logger('Docgen');
|
|
|
| -void _writeIndexableToFile(Indexable result, bool outputToYaml) {
|
| - var outputFile = result.fileName;
|
| - var output;
|
| - if (outputToYaml) {
|
| - output = getYamlString(result.toMap());
|
| - outputFile = outputFile + '.yaml';
|
| - } else {
|
| - output = JSON.encode(result.toMap());
|
| - outputFile = outputFile + '.json';
|
| - }
|
| - _writeToFile(output, outputFile);
|
| -}
|
| -
|
| -/// Returns true if a library name starts with an underscore, and false
|
| -/// otherwise.
|
| -///
|
| -/// An example that starts with _ is _js_helper.
|
| -/// An example that contains ._ is dart._collection.dev
|
| -// This is because LibraryMirror.isPrivate returns `false` all the time.
|
| -bool _isLibraryPrivate(LibraryMirror mirror) {
|
| - var sdkLibrary = LIBRARIES[mirror.simpleName];
|
| - if (sdkLibrary != null) {
|
| - return !sdkLibrary.documented;
|
| - } else if (mirror.simpleName.startsWith('_') ||
|
| - mirror.simpleName.contains('._')) {
|
| - return true;
|
| - }
|
| - return false;
|
| -}
|
| + /// Docgen constructor initializes the link resolver for markdown parsing.
|
| + /// Also initializes the command line arguments.
|
| + ///
|
| + /// [packageRoot] is the packages directory of the directory being analyzed.
|
| + /// If [includeSdk] is `true`, then any SDK libraries explicitly imported will
|
| + /// also be documented.
|
| + /// If [parseSdk] is `true`, then all Dart SDK libraries will be documented.
|
| + /// This option is useful when only the SDK libraries are needed.
|
| + ///
|
| + /// Returned Future completes with true if document generation is successful.
|
| + static Future<bool> generateDocumentation(List<String> files,
|
| + {String packageRoot, bool outputToYaml: true, bool includePrivate: false,
|
| + bool includeSdk: false, bool parseSdk: false, bool append: false,
|
| + String introduction: '', out: _DEFAULT_OUTPUT_DIRECTORY,
|
| + List<String> excludeLibraries : const [],
|
| + bool includeDependentPackages: false}) {
|
| + _excluded = excludeLibraries;
|
| + _includePrivate = includePrivate;
|
| + logger.onRecord.listen((record) => print(record.message));
|
| +
|
| + _ensureOutputDirectory(out, append);
|
| + var updatedPackageRoot = _obtainPackageRoot(packageRoot, parseSdk, files);
|
| +
|
| + var requestedLibraries = _findLibrariesToDocument(files,
|
| + includeDependentPackages);
|
| +
|
| + var allLibraries = []..addAll(requestedLibraries);
|
| + if (includeSdk) {
|
| + allLibraries.addAll(_listSdk());
|
| + }
|
|
|
| -/// A declaration is private if itself is private, or the owner is private.
|
| -// Issue(12202) - A declaration is public even if it's owner is private.
|
| -bool _isHidden(DeclarationMirror mirror) {
|
| - if (mirror is LibraryMirror) {
|
| - return _isLibraryPrivate(mirror);
|
| - } else if (mirror.owner is LibraryMirror) {
|
| - return (mirror.isPrivate || _isLibraryPrivate(mirror.owner));
|
| - } else {
|
| - return (mirror.isPrivate || _isHidden(mirror.owner));
|
| + return getMirrorSystem(allLibraries, packageRoot: updatedPackageRoot,
|
| + parseSdk: parseSdk)
|
| + .then((MirrorSystem mirrorSystem) {
|
| + if (mirrorSystem.libraries.isEmpty) {
|
| + throw new StateError('No library mirrors were created.');
|
| + }
|
| + Indexable.initializeTopLevelLibraries(mirrorSystem);
|
| +
|
| + var availableLibraries = mirrorSystem.libraries.values.where(
|
| + (each) => each.uri.scheme == 'file');
|
| + var availableLibrariesByPath = new Map.fromIterables(
|
| + availableLibraries.map((each) => each.uri),
|
| + availableLibraries);
|
| + var librariesToDocument = requestedLibraries.map(
|
| + (each) => availableLibrariesByPath.putIfAbsent(each,
|
| + () => throw "Missing library $each")).toList();
|
| + librariesToDocument.addAll(
|
| + (includeSdk || parseSdk) ? Indexable._sdkLibraries : []);
|
| + librariesToDocument.removeWhere(
|
| + (x) => _excluded.contains(x.simpleName));
|
| + _documentLibraries(librariesToDocument, includeSdk: includeSdk,
|
| + outputToYaml: outputToYaml, append: append, parseSdk: parseSdk,
|
| + introduction: introduction);
|
| + return true;
|
| + });
|
| }
|
| -}
|
| -
|
| -bool _isVisible(Indexable item) {
|
| - return _includePrivate || !item.isPrivate;
|
| -}
|
| -
|
| -/// Generates MDN comments from database.json.
|
| -void _mdnComment(Indexable item) {
|
| - //Check if MDN is loaded.
|
| - if (_mdn == null) {
|
| - // Reading in MDN related json file.
|
| - var root = findRootDirectory();
|
| - var mdnPath = path.join(root, 'utils/apidoc/mdn/database.json');
|
| - _mdn = JSON.decode(new File(mdnPath).readAsStringSync());
|
| - }
|
| - if (item is Library) return;
|
| - var domAnnotation = item.annotations.firstWhere(
|
| - (e) => e.qualifiedName == 'metadata.DomName', orElse: () => null);
|
| - if (domAnnotation == null) return;
|
| - var domName = domAnnotation.parameters.single;
|
| - var parts = domName.split('.');
|
| - if (parts.length == 2) item.comment = _mdnMemberComment(parts[0], parts[1]);
|
| - if (parts.length == 1) item.comment = _mdnTypeComment(parts[0]);
|
| -}
|
|
|
| -/// Generates the MDN Comment for variables and method DOM elements.
|
| -String _mdnMemberComment(String type, String member) {
|
| - var mdnType = _mdn[type];
|
| - if (mdnType == null) return '';
|
| - var mdnMember = mdnType['members'].firstWhere((e) => e['name'] == member,
|
| - orElse: () => null);
|
| - if (mdnMember == null) return '';
|
| - if (mdnMember['help'] == null || mdnMember['help'] == '') return '';
|
| - if (mdnMember['url'] == null) return '';
|
| - return _htmlMdn(mdnMember['help'], mdnMember['url']);
|
| -}
|
| + /// Writes text to a file in the output directory.
|
| + static void _writeToFile(String text, String filename, {bool append: false}) {
|
| + if (text == null) return;
|
| + Directory dir = new Directory(_outputDirectory);
|
| + if (!dir.existsSync()) {
|
| + dir.createSync();
|
| + }
|
| + if (path.split(filename).length > 1) {
|
| + var splitList = path.split(filename);
|
| + for (int i = 0; i < splitList.length; i++) {
|
| + var level = splitList[i];
|
| + }
|
| + for (var level in path.split(filename)) {
|
| + var subdir = new Directory(path.join(_outputDirectory,
|
| + path.dirname(filename)));
|
| + if (!subdir.existsSync()) {
|
| + subdir.createSync();
|
| + }
|
| + }
|
| + }
|
| + File file = new File(path.join(_outputDirectory, filename));
|
| + file.writeAsStringSync(text,
|
| + mode: append ? FileMode.APPEND : FileMode.WRITE);
|
| + }
|
| +
|
| + /// Creates documentation for filtered libraries.
|
| + static void _documentLibraries(List<LibraryMirror> libs,
|
| + {bool includeSdk: false, bool outputToYaml: true, bool append: false,
|
| + bool parseSdk: false, String introduction: ''}) {
|
| + libs.forEach((lib) {
|
| + // Files belonging to the SDK have a uri that begins with 'dart:'.
|
| + if (includeSdk || !lib.uri.toString().startsWith('dart:')) {
|
| + var library = generateLibrary(lib);
|
| + entityMap[library.name] = library;
|
| + }
|
| + });
|
|
|
| -/// Generates the MDN Comment for class DOM elements.
|
| -String _mdnTypeComment(String type) {
|
| - var mdnType = _mdn[type];
|
| - if (mdnType == null) return '';
|
| - if (mdnType['summary'] == null || mdnType['summary'] == "") return '';
|
| - if (mdnType['srcUrl'] == null) return '';
|
| - return _htmlMdn(mdnType['summary'], mdnType['srcUrl']);
|
| -}
|
| + var filteredEntities = entityMap.values.where(_isFullChainVisible);
|
|
|
| -String _htmlMdn(String content, String url) {
|
| - return '<div class="mdn">' + content.trim() + '<p class="mdn-note">'
|
| - '<a href="' + url.trim() + '">from Mdn</a></p></div>';
|
| -}
|
| + /*var filteredEntities2 = new Set<MirrorBased>();
|
| + for (Map<String, Set<MirrorBased>> firstLevel in mirrorToDocgen.values) {
|
| + for (Set<MirrorBased> items in firstLevel.values) {
|
| + for (MirrorBased item in items) {
|
| + if (_isFullChainVisible(item)) {
|
| + filteredEntities2.add(item);
|
| + }
|
| + }
|
| + }
|
| + }*/
|
| +
|
| + /*print('THHHHHEEE DIFFERENCE IS');
|
| + var set1 = new Set.from(filteredEntities);
|
| + var set2 = new Set.from(filteredEntities2);
|
| + var aResult = set2.difference(set1);
|
| + for (MirrorBased r in aResult) {
|
| + print(' a result is $r and ${r.docName}');
|
| + }*/
|
| + //print(set1.difference(set2));
|
| +
|
| + // Outputs a JSON file with all libraries and their preview comments.
|
| + // This will help the viewer know what libraries are available to read in.
|
| + var libraryMap;
|
| + var linkResolver = (name) => Indexable.globalFixReference(name);
|
| + if (append) {
|
| + var docsDir = listDir(_outputDirectory);
|
| + if (!docsDir.contains('$_outputDirectory/library_list.json')) {
|
| + throw new StateError('No library_list.json');
|
| + }
|
| + libraryMap =
|
| + JSON.decode(new File(
|
| + '$_outputDirectory/library_list.json').readAsStringSync());
|
| + libraryMap['libraries'].addAll(filteredEntities
|
| + .where((e) => e is Library)
|
| + .map((e) => e.previewMap));
|
| + if (introduction.isNotEmpty) {
|
| + var intro = libraryMap['introduction'];
|
| + if (intro.isNotEmpty) intro += '<br/><br/>';
|
| + intro += markdown.markdownToHtml(
|
| + new File(introduction).readAsStringSync(),
|
| + linkResolver: linkResolver, inlineSyntaxes: _MARKDOWN_SYNTAXES);
|
| + libraryMap['introduction'] = intro;
|
| + }
|
| + outputToYaml = libraryMap['filetype'] == 'yaml';
|
| + } else {
|
| + libraryMap = {
|
| + 'libraries' : filteredEntities.where((e) =>
|
| + e is Library).map((e) => e.previewMap).toList(),
|
| + 'introduction' : introduction == '' ?
|
| + '' : markdown.markdownToHtml(new File(introduction)
|
| + .readAsStringSync(), linkResolver: linkResolver,
|
| + inlineSyntaxes: _MARKDOWN_SYNTAXES),
|
| + 'filetype' : outputToYaml ? 'yaml' : 'json'
|
| + };
|
| + }
|
| + _writeToFile(JSON.encode(libraryMap), 'library_list.json');
|
|
|
| -/// Look for the specified name starting with the current member, and
|
| -/// progressively working outward to the current library scope.
|
| -String findElementInScope(String name, LibraryMirror currentLibrary,
|
| - ClassMirror currentClass, MemberMirror currentMember) {
|
| - var packagePrefix = _findPackage(currentLibrary);
|
| - if (packagePrefix != '') packagePrefix += '/';
|
| + // Output libraries and classes to file after all information is generated.
|
| + filteredEntities.where((e) => e is Class || e is Library).forEach((output) {
|
| + _writeIndexableToFile(output, outputToYaml);
|
| + });
|
|
|
| - determineLookupFunc(name) => name.contains('.') ?
|
| - dart2js_util.lookupQualifiedInScope :
|
| - (mirror, name) => mirror.lookupInScope(name);
|
| - var lookupFunc = determineLookupFunc(name);
|
| -
|
| - var memberScope = currentMember == null ?
|
| - null : lookupFunc(currentMember, name);
|
| - if (memberScope != null) return packagePrefix + docName(memberScope);
|
| -
|
| - var classScope = currentClass;
|
| - while (classScope != null) {
|
| - var classFunc = lookupFunc(currentClass, name);
|
| - if (classFunc != null) return packagePrefix + docName(classFunc);
|
| - classScope = classScope.superclass;
|
| - }
|
| -
|
| - var libraryScope = currentLibrary == null ?
|
| - null : lookupFunc(currentLibrary, name);
|
| - if (libraryScope != null) return packagePrefix + docName(libraryScope);
|
| -
|
| - // Look in the dart core library scope.
|
| - var coreScope = _coreLibrary == null? null : lookupFunc(_coreLibrary, name);
|
| - if (coreScope != null) return packagePrefix + docName(_coreLibrary);
|
| -
|
| - // If it's a reference that starts with a another library name, then it
|
| - // looks for a match of that library name in the other sdk libraries.
|
| - if(name.contains('.')) {
|
| - var index = name.indexOf('.');
|
| - var libraryName = name.substring(0, index);
|
| - var remainingName = name.substring(index + 1);
|
| - foundLibraryName(library) => library.uri.pathSegments[0] == libraryName;
|
| -
|
| - if (_sdkLibraries.any(foundLibraryName)) {
|
| - var library = _sdkLibraries.singleWhere(foundLibraryName);
|
| - // Look to see if it's a fully qualified library name.
|
| - var scope = determineLookupFunc(remainingName)(library, remainingName);
|
| - if (scope != null) return packagePrefix + docName(scope);
|
| + // Outputs all the qualified names documented with their type.
|
| + // This will help generate search results.
|
| + _writeToFile(filteredEntities.map((e) =>
|
| + '${e.qualifiedName} ${e.typeName}').join('\n') + '\n',
|
| + 'index.txt', append: append);
|
| + var index = new Map.fromIterables(
|
| + filteredEntities.map((e) => e.qualifiedName),
|
| + filteredEntities.map((e) => e.typeName));
|
| + if (append) {
|
| + var previousIndex =
|
| + JSON.decode(new File(
|
| + '$_outputDirectory/index.json').readAsStringSync());
|
| + index.addAll(previousIndex);
|
| }
|
| + _writeToFile(JSON.encode(index), 'index.json');
|
| }
|
| - return null;
|
| -}
|
|
|
| -// HTML escaped version of '<' character.
|
| -final _LESS_THAN = '<';
|
| -
|
| -/// Chunk the provided name into individual parts to be resolved. We take a
|
| -/// simplistic approach to chunking, though, we break at " ", ",", "<"
|
| -/// and ">". All other characters are grouped into the name to be resolved.
|
| -/// As a result, these characters will all be treated as part of the item to be
|
| -/// resolved (aka the * is interpreted literally as a *, not as an indicator for
|
| -/// bold <em>.
|
| -List<String> _tokenizeComplexReference(String name) {
|
| - var tokens = [];
|
| - var append = false;
|
| - var index = 0;
|
| - while(index < name.length) {
|
| - if (name.indexOf(_LESS_THAN, index) == index) {
|
| - tokens.add(_LESS_THAN);
|
| - append = false;
|
| - index += _LESS_THAN.length;
|
| - } else if (name[index] == ' ' || name[index] == ',' ||
|
| - name[index] == '>') {
|
| - tokens.add(name[index]);
|
| - append = false;
|
| - index++;
|
| + static void _writeIndexableToFile(Indexable result, bool outputToYaml) {
|
| + var outputFile = result.fileName;
|
| + var output;
|
| + if (outputToYaml) {
|
| + output = getYamlString(result.toMap());
|
| + outputFile = outputFile + '.yaml';
|
| } else {
|
| - if (append) {
|
| - tokens[tokens.length - 1] = tokens.last + name[index];
|
| - } else {
|
| - tokens.add(name[index]);
|
| - append = true;
|
| - }
|
| - index++;
|
| + output = JSON.encode(result.toMap());
|
| + outputFile = outputFile + '.json';
|
| }
|
| + _writeToFile(output, outputFile);
|
| }
|
| - return tokens;
|
| -}
|
|
|
| -/// This is a more complex reference. Try to break up if its of the form A<B>
|
| -/// where A is an alphanumeric string and B is an A, a list of B ("B, B, B"),
|
| -/// or of the form A<B>. Note: unlike other the other markdown-style links, all
|
| -/// text inside the square brackets is treated as part of the link (aka the * is
|
| -/// interpreted literally as a *, not as a indicator for bold <em>.
|
| -///
|
| -/// Example: [foo<_bar_>] will produce
|
| -/// <a>resolvedFoo</a><<a>resolved_bar_</a>> rather than an italicized
|
| -/// version of resolvedBar.
|
| -markdown.Node _fixComplexReference(String name, LibraryMirror currentLibrary,
|
| - ClassMirror currentClass, MemberMirror currentMember) {
|
| - // Parse into multiple elements we can try to resolve.
|
| - var tokens = _tokenizeComplexReference(name);
|
| -
|
| - // Produce an html representation of our elements. Group unresolved and plain
|
| - // text are grouped into "link" elements so they display as code.
|
| - final textElements = [' ', ',', '>', _LESS_THAN];
|
| - var accumulatedHtml = '';
|
| -
|
| - for (var token in tokens) {
|
| - bool added = false;
|
| - if (!textElements.contains(token)) {
|
| - String elementName = findElementInScope(token, currentLibrary,
|
| - currentClass, currentMember);
|
| - if (elementName != null) {
|
| - accumulatedHtml += markdown.renderToHtml([new markdown.Element.text(
|
| - 'a', elementName)]);
|
| - added = true;
|
| - }
|
| - }
|
| - if (!added) {
|
| - accumulatedHtml += token;
|
| - }
|
| + /// Set the location of the ouput directory, and ensure that the location is
|
| + /// available on the file system.
|
| + static void _ensureOutputDirectory(String outputDirectory, bool append) {
|
| + _outputDirectory = outputDirectory;
|
| + if (!append) {
|
| + var dir = new Directory(_outputDirectory);
|
| + if (dir.existsSync()) dir.deleteSync(recursive: true);
|
| + }
|
| }
|
| - return new markdown.Text(accumulatedHtml);
|
| -}
|
|
|
| -/// Converts all [foo] references in comments to <a>libraryName.foo</a>.
|
| -markdown.Node fixReference(String name, LibraryMirror currentLibrary,
|
| - ClassMirror currentClass, MemberMirror currentMember) {
|
| - // Attempt the look up the whole name up in the scope.
|
| - String elementName =
|
| - findElementInScope(name, currentLibrary, currentClass, currentMember);
|
| - if (elementName != null) {
|
| - return new markdown.Element.text('a', elementName);
|
| - }
|
| - return _fixComplexReference(name, currentLibrary, currentClass,
|
| - currentMember);
|
| -}
|
|
|
| -markdown.Node fixReferenceWithScope(String name, DeclarationMirror scope) {
|
| - if (scope is LibraryMirror) return fixReference(name, scope, null, null);
|
| - if (scope is ClassMirror)
|
| - return fixReference(name, scope.library, scope, null);
|
| - if (scope is MemberMirror) {
|
| - var owner = scope.owner;
|
| - if (owner is ClassMirror) {
|
| - return fixReference(name, owner.library, owner, scope);
|
| - } else {
|
| - return fixReference(name, owner, null, scope);
|
| + static String get _rootDirectory {
|
| + var scriptDir = path.absolute(path.dirname(Platform.script.toFilePath()));
|
| + var root = scriptDir;
|
| + while(path.basename(root) != 'dart') {
|
| + root = path.dirname(root);
|
| }
|
| - }
|
| - return null;
|
| -}
|
| -
|
| -/// Writes text to a file in the output directory.
|
| -void _writeToFile(String text, String filename, {bool append: false}) {
|
| - if (text == null) return;
|
| - Directory dir = new Directory(_outputDirectory);
|
| - if (!dir.existsSync()) {
|
| - dir.createSync();
|
| - }
|
| - if (path.split(filename).length > 1) {
|
| - var splitList = path.split(filename);
|
| - for (int i = 0; i < splitList.length; i++) {
|
| - var level = splitList[i];
|
| + return root;
|
| + }
|
| +
|
| + /// Analyzes set of libraries and provides a mirror system which can be used
|
| + /// for static inspection of the source code.
|
| + static Future<MirrorSystem> _analyzeLibraries(List<Uri> libraries,
|
| + String libraryRoot, {String packageRoot}) {
|
| + SourceFileProvider provider = new CompilerSourceFileProvider();
|
| + api.DiagnosticHandler diagnosticHandler =
|
| + (new FormattingDiagnosticHandler(provider)
|
| + ..showHints = false
|
| + ..showWarnings = false)
|
| + .diagnosticHandler;
|
| + Uri libraryUri = new Uri.file(appendSlash(libraryRoot));
|
| + Uri packageUri = null;
|
| + if (packageRoot != null) {
|
| + packageUri = new Uri.file(appendSlash(packageRoot));
|
| }
|
| - for (var level in path.split(filename)) {
|
| - var subdir = new Directory(path.join(_outputDirectory,
|
| - path.dirname(filename)));
|
| - if (!subdir.existsSync()) {
|
| - subdir.createSync();
|
| + return dart2js.analyze(libraries, libraryUri, packageUri,
|
| + provider.readStringFromUri, diagnosticHandler,
|
| + ['--preserve-comments', '--categories=Client,Server'])
|
| + ..catchError((error) {
|
| + logger.severe('Error: Failed to create mirror system. ');
|
| + // TODO(janicejl): Use the stack trace package when bug is resolved.
|
| + // Currently, a string is thrown when it fails to create a mirror
|
| + // system, and it is not possible to use the stack trace. BUG(#11622)
|
| + // To avoid printing the stack trace.
|
| + exit(1);
|
| + });
|
| + }
|
| +
|
| + /// For this run of docgen, determine the packageRoot value.
|
| + ///
|
| + /// If packageRoot is not explicitly passed, we examine the files we're
|
| + /// documenting to attempt to find a package root.
|
| + static String _obtainPackageRoot(String packageRoot, bool parseSdk,
|
| + List<String> files) {
|
| + if (packageRoot == null && !parseSdk) {
|
| + // TODO(efortuna): This logic seems not very robust, but it's from the
|
| + // original version of the code, pre-refactor, so I'm leavingt it for now.
|
| + // Revisit to make more robust.
|
| + var type = FileSystemEntity.typeSync(files.first);
|
| + if (type == FileSystemEntityType.DIRECTORY) {
|
| + var files2 = listDir(files.first, recursive: true);
|
| + // Return '' means that there was no pubspec.yaml and therefor no p
|
| + // ackageRoot.
|
| + packageRoot = files2.firstWhere((f) =>
|
| + f.endsWith('${path.separator}pubspec.yaml'), orElse: () => '');
|
| + if (packageRoot != '') {
|
| + packageRoot = path.join(path.dirname(packageRoot), 'packages');
|
| + }
|
| + } else if (type == FileSystemEntityType.FILE) {
|
| + logger.warning('WARNING: No package root defined. If Docgen fails, try '
|
| + 'again by setting the --package-root option.');
|
| }
|
| + }
|
| + logger.info('Package Root: ${packageRoot}');
|
| + return packageRoot;
|
| + }
|
| +
|
| + /// Given the user provided list of items to document, expand all directories
|
| + /// to document out into specific files and add any dependent packages for
|
| + /// documentation if desired.
|
| + static List<Uri> _findLibrariesToDocument(List<String> args,
|
| + bool includeDependentPackages) {
|
| + if (includeDependentPackages) {
|
| + args.addAll(_allDependentPackageDirs(args.first));
|
| }
|
| - }
|
| - File file = new File(path.join(_outputDirectory, filename));
|
| - file.writeAsStringSync(text, mode: append ? FileMode.APPEND : FileMode.WRITE);
|
| -}
|
|
|
| -/// Transforms the map by calling toMap on each value in it.
|
| -Map recurseMap(Map inputMap) {
|
| - var outputMap = {};
|
| - inputMap.forEach((key, value) {
|
| - if (value is Map) {
|
| - outputMap[key] = recurseMap(value);
|
| - } else {
|
| - outputMap[key] = value.toMap();
|
| + var libraries = new List<Uri>();
|
| + for (var arg in args) {
|
| + if (FileSystemEntity.typeSync(arg) == FileSystemEntityType.FILE) {
|
| + if (arg.endsWith('.dart')) {
|
| + libraries.add(new Uri.file(path.absolute(arg)));
|
| + logger.info('Added to libraries: ${libraries.last}');
|
| + }
|
| + } else {
|
| + libraries.addAll(_findFilesToDocumentInPackage(arg));
|
| + }
|
| }
|
| - });
|
| - return outputMap;
|
| + return libraries;
|
| + }
|
| +
|
| + /// Given a package name, explore the directory and pull out all top level
|
| + /// library files in the "lib" directory to document.
|
| + static List<Uri> _findFilesToDocumentInPackage(String packageName) {
|
| + var libraries = [];
|
| + // To avoid anaylzing package files twice, only files with paths not
|
| + // containing '/packages' will be added. The only exception is if the file
|
| + // to analyze already has a '/package' in its path.
|
| + var files = listDir(packageName, recursive: true).where(
|
| + (f) => f.endsWith('.dart') && (!f.contains('${path.separator}packages')
|
| + || packageName.contains('${path.separator}packages'))).toList();
|
| +
|
| + files.forEach((String f) {
|
| + // Only include libraries at the top level of "lib"
|
| + if (path.basename(path.dirname(f)) == 'lib') {
|
| + // Only add the file if it does not contain 'part of'
|
| + // TODO(janicejl): Remove when Issue(12406) is resolved.
|
| + var contents = new File(f).readAsStringSync();
|
| + if (!(contents.contains(new RegExp('\npart of ')) ||
|
| + contents.startsWith(new RegExp('part of ')))) {
|
| + libraries.add(new Uri.file(path.normalize(path.absolute(f))));
|
| + logger.info('Added to libraries: $f');
|
| + }
|
| + }
|
| + });
|
| + return libraries;
|
| + }
|
| +
|
| + /// All of the directories for our dependent packages
|
| + static List<String> _allDependentPackageDirs(String packageDirectory) {
|
| + var dependentsJson = Process.runSync('pub', ['list-package-dirs'],
|
| + workingDirectory: packageDirectory, runInShell: true);
|
| + if (dependentsJson.exitCode != 0) {
|
| + print(dependentsJson.stderr);
|
| + }
|
| + var dependents = JSON.decode(dependentsJson.stdout)['packages'];
|
| + return dependents.values.toList();
|
| + }
|
| +
|
| + /// For all the libraries, return a list of the libraries that are part of
|
| + /// the SDK.
|
| + static List<Uri> _listSdk() {
|
| + var sdk = new List<Uri>();
|
| + LIBRARIES.forEach((String name, LibraryInfo info) {
|
| + if (info.documented) {
|
| + sdk.add(Uri.parse('dart:$name'));
|
| + logger.info('Add to SDK: ${sdk.last}');
|
| + }
|
| + });
|
| + return sdk;
|
| + }
|
| +
|
| + static bool _isFullChainVisible(MirrorBased item) {
|
| + // TODO: reconcile with isVisible
|
| + var result = _includePrivate || (!item.isPrivate && (item.owner != null ?
|
| + _isFullChainVisible(item.owner) : true));
|
| + return result;
|
| + }
|
| +
|
| + /// Currently left public for testing purposes. :-/
|
| + static Library generateLibrary(dart2js.Dart2JsLibraryMirror library) {
|
| + var result = new Library(library);
|
| + result._findPackage(library);
|
| + logger.fine('Generated library for ${result.name}');
|
| + return result;
|
| + }
|
| }
|
|
|
| -/// A type for the function that generates a comment from a mirror.
|
| -typedef String CommentGenerator(Mirror m);
|
| +/// An item that is categorized in our mirrorToDocgen map, as a distinct,
|
| +/// searchable element.
|
| +///
|
| +/// These are items that refer to concrete entities (a Class, for example,
|
| +/// but not a Type, which is a "pointer" to a class) that we wish to be
|
| +/// globally resolvable. This includes things such as class methods and
|
| +/// variables, but parameters for methods are not "Indexable" as we do not want
|
| +/// the user to be able to search for a method based on its parameter names!
|
| +/// The set of indexable items also includes Typedefs, since the user can refer
|
| +/// to them as concrete entities in a particular scope.
|
| +class Indexable extends MirrorBased {
|
| + /// The dart:core library, which contains all types that are always available
|
| + /// without import.
|
| + static Library _coreLibrary;
|
| +
|
| + /// Set of libraries declared in the SDK, so libraries that can be accessed
|
| + /// when running dart by default.
|
| + static Iterable<LibraryMirror> _sdkLibraries;
|
|
|
| -/// A class representing all programming constructs, like library or class.
|
| -class Indexable {
|
| String get qualifiedName => fileName;
|
| bool isPrivate;
|
| DeclarationMirror mirror;
|
| + /// The comment text pre-resolution. We keep this around because inherited
|
| + /// methods need to resolve links differently from the superclass.
|
| + String _unresolvedComment = '';
|
| +
|
| + // TODO(janicejl): Make MDN content generic or pluggable. Maybe move
|
| + // MDN-specific code to its own library that is imported into the default
|
| + // impl?
|
| + /// Map of all the comments for dom elements from MDN.
|
| + static Map _mdn;
|
|
|
| Indexable(this.mirror) {
|
| this.isPrivate = _isHidden(mirror);
|
| +
|
| + var map = mirrorToDocgen[this.mirror.qualifiedName];
|
| + if (map == null) map = new Map<String, Set<MirrorBased>>();
|
| +
|
| + var set = map[owner.docName];
|
| + if (set == null) set = new Set<MirrorBased>();
|
| + set.add(this);
|
| + map[owner.docName] = set;
|
| + mirrorToDocgen[this.mirror.qualifiedName] = map;
|
| + }
|
| +
|
| + /** Walk up the owner chain to find the owning library. */
|
| + Library _getOwningLibrary(Indexable owner) {
|
| + if (owner is Library) return owner;
|
| + // TODO: is this needed?
|
| + if (owner is DummyMirror) return getDocgenObject(owner.mirror.library);
|
| + return _getOwningLibrary(owner.owner);
|
| + }
|
| +
|
| + static initializeTopLevelLibraries(MirrorSystem mirrorSystem) {
|
| + _sdkLibraries = mirrorSystem.libraries.values.where(
|
| + (each) => each.uri.scheme == 'dart');
|
| + _coreLibrary = new Library(_sdkLibraries.singleWhere((lib) =>
|
| + lib.uri.toString().startsWith('dart:core')));
|
| + }
|
| +
|
| + markdown.Node fixReferenceWithScope(String name) => null;
|
| +
|
| + /// Converts all [foo] references in comments to <a>libraryName.foo</a>.
|
| + markdown.Node fixReference(String name) {
|
| + // Attempt the look up the whole name up in the scope.
|
| + String elementName = findElementInScope(name);
|
| + if (elementName != null) {
|
| + return new markdown.Element.text('a', elementName);
|
| + }
|
| + return _fixComplexReference(name);
|
| }
|
|
|
| + /// Look for the specified name starting with the current member, and
|
| + /// progressively working outward to the current library scope.
|
| + String findElementInScope(String name) =>
|
| + _findElementInScope(name, packagePrefix);
|
| +
|
| + static determineLookupFunc(name) => name.contains('.') ?
|
| + dart2js_util.lookupQualifiedInScope :
|
| + (mirror, name) => mirror.lookupInScope(name);
|
| +
|
| // The qualified name (for URL purposes) and the file name are the same,
|
| // of the form packageName/ClassName or packageName/ClassName.methodName.
|
| // This defines both the URL and the directory structure.
|
| - String get fileName => packagePrefix + ownerPrefix + name;
|
| -
|
| - Indexable get owningEntity => entityMap[owner];
|
| + String get fileName {
|
| + return packagePrefix + ownerPrefix + name;
|
| + }
|
|
|
| - String get ownerPrefix => owningEntity == null ?
|
| - (owner == null || owner.isEmpty ? '' : owner + '.') :
|
| - owningEntity.qualifiedName + '.';
|
| + String get ownerPrefix => owner.docName != '' ? owner.docName + '.' : '';
|
|
|
| String get packagePrefix => '';
|
|
|
| @@ -727,9 +673,10 @@ class Indexable {
|
|
|
| String get comment {
|
| if (_comment != null) return _comment;
|
| - _comment = _commentToHtml(mirror);
|
| +
|
| + _comment = _commentToHtml();
|
| if (_comment.isEmpty) {
|
| - _mdnComment(this);
|
| + _comment = _mdnComment();
|
| }
|
| return _comment;
|
| }
|
| @@ -738,8 +685,54 @@ class Indexable {
|
|
|
| String get name => mirror.simpleName;
|
|
|
| - /// Qualified Name of the owner of this Indexable Item.
|
| - String get owner => docName(mirror.owner);
|
| + MirrorBased get owner => new DummyMirror(mirror.owner);
|
| +
|
| + /// Generates MDN comments from database.json.
|
| + String _mdnComment() {
|
| + //Check if MDN is loaded.
|
| + if (_mdn == null) {
|
| + // Reading in MDN related json file.
|
| + var root = _Generator._rootDirectory;
|
| + var mdnPath = path.join(root, 'utils/apidoc/mdn/database.json');
|
| + _mdn = JSON.decode(new File(mdnPath).readAsStringSync());
|
| + }
|
| + // TODO: refactor OOP
|
| + if (this is Library) return '';
|
| + var domAnnotation = this.annotations.firstWhere(
|
| + (e) => e.mirror.qualifiedName == 'metadata.DomName',
|
| + orElse: () => null);
|
| + if (domAnnotation == null) return '';
|
| + var domName = domAnnotation.parameters.single;
|
| + var parts = domName.split('.');
|
| + if (parts.length == 2) return _mdnMemberComment(parts[0], parts[1]);
|
| + if (parts.length == 1) return _mdnTypeComment(parts[0]);
|
| + }
|
| +
|
| + /// Generates the MDN Comment for variables and method DOM elements.
|
| + String _mdnMemberComment(String type, String member) {
|
| + var mdnType = _mdn[type];
|
| + if (mdnType == null) return '';
|
| + var mdnMember = mdnType['members'].firstWhere((e) => e['name'] == member,
|
| + orElse: () => null);
|
| + if (mdnMember == null) return '';
|
| + if (mdnMember['help'] == null || mdnMember['help'] == '') return '';
|
| + if (mdnMember['url'] == null) return '';
|
| + return _htmlMdn(mdnMember['help'], mdnMember['url']);
|
| + }
|
| +
|
| + /// Generates the MDN Comment for class DOM elements.
|
| + String _mdnTypeComment(String type) {
|
| + var mdnType = _mdn[type];
|
| + if (mdnType == null) return '';
|
| + if (mdnType['summary'] == null || mdnType['summary'] == "") return '';
|
| + if (mdnType['srcUrl'] == null) return '';
|
| + return _htmlMdn(mdnType['summary'], mdnType['srcUrl']);
|
| + }
|
| +
|
| + String _htmlMdn(String content, String url) {
|
| + return '<div class="mdn">' + content.trim() + '<p class="mdn-note">'
|
| + '<a href="' + url.trim() + '">from Mdn</a></p></div>';
|
| + }
|
|
|
| /// The type of this member to be used in index.txt.
|
| String get typeName => '';
|
| @@ -754,12 +747,7 @@ class Indexable {
|
| return finalMap;
|
| }
|
|
|
| - /// Returns any documentation comments associated with a mirror with
|
| - /// simple markdown converted to html.
|
| - ///
|
| - /// It's possible to have a comment that comes from one mirror applied to
|
| - /// another, in the case of an inherited comment.
|
| - String _commentToHtml(itemToDocument) {
|
| + String _getCommentText() {
|
| String commentText;
|
| mirror.metadata.forEach((metadata) {
|
| if (metadata is CommentInstanceMirror) {
|
| @@ -773,11 +761,24 @@ class Indexable {
|
| }
|
| }
|
| });
|
| + return commentText;
|
| + }
|
|
|
| - var linkResolver = (name) => fixReferenceWithScope(name, itemToDocument);
|
| + /// Returns any documentation comments associated with a mirror with
|
| + /// simple markdown converted to html.
|
| + ///
|
| + /// By default we resolve any comment references within our own scope.
|
| + /// However, if a method is inherited, we want the inherited comments, but
|
| + /// links to the subclasses's version of the methods.
|
| + String _commentToHtml([Indexable resolvingScope]) {
|
| + if (resolvingScope == null) resolvingScope = this;
|
| + var commentText = _getCommentText();
|
| + _unresolvedComment = commentText;
|
| +
|
| + var linkResolver = (name) => resolvingScope.fixReferenceWithScope(name);
|
| commentText = commentText == null ? '' :
|
| markdown.markdownToHtml(commentText.trim(), linkResolver: linkResolver,
|
| - inlineSyntaxes: markdownSyntaxes);
|
| + inlineSyntaxes: _MARKDOWN_SYNTAXES);
|
| return commentText;
|
| }
|
|
|
| @@ -785,21 +786,16 @@ class Indexable {
|
| /// The optional parameter [containingLibrary] is contains data for variables
|
| /// defined at the top level of a library (potentially for exporting
|
| /// purposes).
|
| - Map<String, Variable> _createVariables(Map<String,
|
| - VariableMirror> mirrorMap, [Library containingLibrary]) {
|
| + Map<String, Variable> _createVariables(Map<String, VariableMirror> mirrorMap,
|
| + Indexable owner) {
|
| var data = {};
|
| // TODO(janicejl): When map to map feature is created, replace the below
|
| // with a filter. Issue(#9590).
|
| mirrorMap.forEach((String mirrorName, VariableMirror mirror) {
|
| - if (_includePrivate || !_isHidden(mirror)) {
|
| - if (containingLibrary != null && mirror.owner.qualifiedName !=
|
| - containingLibrary.mirror.qualifiedName) {
|
| - entityMap[docName(mirror)] = new ExportedVariable(mirrorName, mirror,
|
| - containingLibrary);
|
| - } else {
|
| - entityMap[docName(mirror)] = new Variable(mirrorName, mirror);
|
| - }
|
| - data[mirrorName] = entityMap[docName(mirror)];
|
| + if (_Generator._includePrivate || !_isHidden(mirror)) {
|
| + var variable = new Variable(mirrorName, mirror, owner);
|
| + entityMap[variable.docName] = variable;
|
| + data[mirrorName] = entityMap[variable.docName];
|
| }
|
| });
|
| return data;
|
| @@ -809,25 +805,25 @@ class Indexable {
|
| /// The optional parameter [containingLibrary] is contains data for variables
|
| /// defined at the top level of a library (potentially for exporting
|
| /// purposes).
|
| - MethodGroup _createMethods(Map<String, MethodMirror> mirrorMap,
|
| - [Library containingLibrary]) {
|
| - var group = new MethodGroup();
|
| + Map<String, Method> _createMethods(Map<String, MethodMirror> mirrorMap,
|
| + Indexable owner) {
|
| + var group = new Map<String, Method>();
|
| mirrorMap.forEach((String mirrorName, MethodMirror mirror) {
|
| - if (_includePrivate || !mirror.isPrivate) {
|
| - group.addMethod(mirror, containingLibrary);
|
| + if (_Generator._includePrivate || !mirror.isPrivate) {
|
| + var method = new Method(mirror, owner);
|
| + entityMap[method.docName] = method;
|
| + group[mirror.simpleName] = method;
|
| }
|
| });
|
| return group;
|
| }
|
|
|
| /// Returns a map of [Parameter] objects constructed from [mirrorList].
|
| - Map<String, Parameter> _createParameters(List<ParameterMirror> mirrorList) {
|
| + Map<String, Parameter> _createParameters(List<ParameterMirror> mirrorList,
|
| + [Indexable owner]) {
|
| var data = {};
|
| mirrorList.forEach((ParameterMirror mirror) {
|
| - data[mirror.simpleName] = new Parameter(mirror.simpleName,
|
| - mirror.isOptional, mirror.isNamed, mirror.hasDefaultValue,
|
| - _createType(mirror.type), mirror.defaultValue,
|
| - _createAnnotations(mirror));
|
| + data[mirror.simpleName] = new Parameter(mirror, owner);
|
| });
|
| return data;
|
| }
|
| @@ -836,51 +832,196 @@ class Indexable {
|
| Map<String, Generic> _createGenerics(ClassMirror mirror) {
|
| return new Map.fromIterable(mirror.typeVariables,
|
| key: (e) => e.toString(),
|
| - value: (e) => new Generic(e.toString(), e.upperBound.qualifiedName));
|
| + value: (e) => new Generic(e));
|
| }
|
|
|
| - /// Returns a single [Type] object constructed from the Method.returnType
|
| - /// Type mirror.
|
| - Type _createType(TypeMirror mirror) {
|
| - return new Type(docName(mirror), _createTypeGenerics(mirror));
|
| + /// Return an informative [Object.toString] for debugging.
|
| + String toString() => "${super.toString()}(${name.toString()})";
|
| +
|
| + /// Return a map representation of this type.
|
| + Map toMap() {}
|
| +
|
| +
|
| + /// A declaration is private if itself is private, or the owner is private.
|
| + // Issue(12202) - A declaration is public even if it's owner is private.
|
| + bool _isHidden(DeclarationMirror mirror) {
|
| + if (mirror is LibraryMirror) {
|
| + return _isLibraryPrivate(mirror);
|
| + } else if (mirror.owner is LibraryMirror) {
|
| + return (mirror.isPrivate || _isLibraryPrivate(mirror.owner)
|
| + || mirror.isNameSynthetic);
|
| + } else {
|
| + return (mirror.isPrivate || _isHidden(mirror.owner)
|
| + || owner.mirror.isNameSynthetic);
|
| + }
|
| }
|
|
|
| - /// Returns a list of [Type] objects constructed from TypeMirrors.
|
| - List<Type> _createTypeGenerics(TypeMirror mirror) {
|
| - if (mirror is ClassMirror && !mirror.isTypedef) {
|
| - var innerList = [];
|
| - mirror.typeArguments.forEach((e) {
|
| - innerList.add(new Type(docName(e), _createTypeGenerics(e)));
|
| - });
|
| - return innerList;
|
| + /// Returns true if a library name starts with an underscore, and false
|
| + /// otherwise.
|
| + ///
|
| + /// An example that starts with _ is _js_helper.
|
| + /// An example that contains ._ is dart._collection.dev
|
| + // This is because LibraryMirror.isPrivate returns `false` all the time.
|
| + bool _isLibraryPrivate(LibraryMirror mirror) {
|
| + var sdkLibrary = LIBRARIES[mirror.simpleName];
|
| + if (sdkLibrary != null) {
|
| + return !sdkLibrary.documented;
|
| + } else if (mirror.simpleName.startsWith('_') ||
|
| + mirror.simpleName.contains('._')) {
|
| + return true;
|
| }
|
| - return [];
|
| + return false;
|
| }
|
|
|
| - /// Returns a list of meta annotations assocated with a mirror.
|
| - List<Annotation> _createAnnotations(DeclarationMirror mirror) {
|
| - var annotationMirrors = mirror.metadata.where((e) =>
|
| - e is dart2js.Dart2JsConstructedConstantMirror);
|
| - var annotations = [];
|
| - annotationMirrors.forEach((annotation) {
|
| - var parameterList = annotation.type.variables.values
|
| - .where((e) => e.isFinal)
|
| - .map((e) => annotation.getField(e.simpleName).reflectee)
|
| - .where((e) => e != null)
|
| - .toList();
|
| - if (!skippedAnnotations.contains(docName(annotation.type))) {
|
| - annotations.add(new Annotation(docName(annotation.type),
|
| - parameterList));
|
| + ////// Top level resolution functions
|
| + /// Converts all [foo] references in comments to <a>libraryName.foo</a>.
|
| + static markdown.Node globalFixReference(String name) {
|
| + // Attempt the look up the whole name up in the scope.
|
| + String elementName = _findElementInScope(name, '');
|
| + if (elementName != null) {
|
| + return new markdown.Element.text('a', elementName);
|
| + }
|
| + return _fixComplexReference(name);
|
| + }
|
| +
|
| + /// This is a more complex reference. Try to break up if its of the form A<B>
|
| + /// where A is an alphanumeric string and B is an A, a list of B ("B, B, B"),
|
| + /// or of the form A<B>. Note: unlike other the other markdown-style links,
|
| + /// all text inside the square brackets is treated as part of the link (aka
|
| + /// the * is interpreted literally as a *, not as a indicator for bold <em>.
|
| + ///
|
| + /// Example: [foo<_bar_>] will produce
|
| + /// <a>resolvedFoo</a><<a>resolved_bar_</a>> rather than an italicized
|
| + /// version of resolvedBar.
|
| + static markdown.Node _fixComplexReference(String name) {
|
| + // Parse into multiple elements we can try to resolve.
|
| + var tokens = _tokenizeComplexReference(name);
|
| +
|
| + // Produce an html representation of our elements. Group unresolved and
|
| + // plain text are grouped into "link" elements so they display as code.
|
| + final textElements = [' ', ',', '>', _LESS_THAN];
|
| + var accumulatedHtml = '';
|
| +
|
| + for (var token in tokens) {
|
| + bool added = false;
|
| + if (!textElements.contains(token)) {
|
| + String elementName = _findElementInScope(token, '');
|
| + if (elementName != null) {
|
| + accumulatedHtml += markdown.renderToHtml([new markdown.Element.text(
|
| + 'a', elementName)]);
|
| + added = true;
|
| + }
|
| + }
|
| + if (!added) {
|
| + accumulatedHtml += token;
|
| + }
|
| + }
|
| + return new markdown.Text(accumulatedHtml);
|
| + }
|
| +
|
| +
|
| + // HTML escaped version of '<' character.
|
| + static final _LESS_THAN = '<';
|
| +
|
| + /// Chunk the provided name into individual parts to be resolved. We take a
|
| + /// simplistic approach to chunking, though, we break at " ", ",", "<"
|
| + /// and ">". All other characters are grouped into the name to be resolved.
|
| + /// As a result, these characters will all be treated as part of the item to
|
| + /// be resolved (aka the * is interpreted literally as a *, not as an
|
| + /// indicator for bold <em>.
|
| + static List<String> _tokenizeComplexReference(String name) {
|
| + var tokens = [];
|
| + var append = false;
|
| + var index = 0;
|
| + while(index < name.length) {
|
| + if (name.indexOf(_LESS_THAN, index) == index) {
|
| + tokens.add(_LESS_THAN);
|
| + append = false;
|
| + index += _LESS_THAN.length;
|
| + } else if (name[index] == ' ' || name[index] == ',' ||
|
| + name[index] == '>') {
|
| + tokens.add(name[index]);
|
| + append = false;
|
| + index++;
|
| + } else {
|
| + if (append) {
|
| + tokens[tokens.length - 1] = tokens.last + name[index];
|
| + } else {
|
| + tokens.add(name[index]);
|
| + append = true;
|
| + }
|
| + index++;
|
| + }
|
| + }
|
| + return tokens;
|
| + }
|
| +
|
| + static String _findElementInScope(String name, String packagePrefix) {
|
| + var lookupFunc = determineLookupFunc(name);
|
| + // Look in the dart core library scope.
|
| + var coreScope = _coreLibrary == null? null :
|
| + lookupFunc(_coreLibrary.mirror, name);
|
| + if (coreScope != null) return packagePrefix + _coreLibrary.docName;
|
| +
|
| + // If it's a reference that starts with a another library name, then it
|
| + // looks for a match of that library name in the other sdk libraries.
|
| + if(name.contains('.')) {
|
| + var index = name.indexOf('.');
|
| + var libraryName = name.substring(0, index);
|
| + var remainingName = name.substring(index + 1);
|
| + foundLibraryName(library) => library.uri.pathSegments[0] == libraryName;
|
| +
|
| + if (_sdkLibraries.any(foundLibraryName)) {
|
| + var library = _sdkLibraries.singleWhere(foundLibraryName);
|
| + // Look to see if it's a fully qualified library name.
|
| + var scope = determineLookupFunc(remainingName)(library, remainingName);
|
| + if (scope != null) {
|
| + var result = getDocgenObject(scope);
|
| + if (result is DummyMirror) {
|
| + return packagePrefix + result.docName;
|
| + } else {
|
| + return result.packagePrefix + result.docName;
|
| + }
|
| + }
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + Map expandMethodMap(Map<String, Method> mapToExpand) => {
|
| + 'setters': recurseMap(_filterMap(new Map(), mapToExpand,
|
| + (key, val) => val.mirror.isSetter)),
|
| + 'getters': recurseMap(_filterMap(new Map(), mapToExpand,
|
| + (key, val) => val.mirror.isGetter)),
|
| + 'constructors': recurseMap(_filterMap(new Map(), mapToExpand,
|
| + (key, val) => val.mirror.isConstructor)),
|
| + 'operators': recurseMap(_filterMap(new Map(), mapToExpand,
|
| + (key, val) => val.mirror.isOperator)),
|
| + 'methods': recurseMap(_filterMap(new Map(), mapToExpand,
|
| + (key, val) => val.mirror.isRegularMethod && !val.mirror.isOperator))
|
| + };
|
| +
|
| + /// Transforms the map by calling toMap on each value in it.
|
| + Map recurseMap(Map inputMap) {
|
| + var outputMap = {};
|
| + inputMap.forEach((key, value) {
|
| + if (value is Map) {
|
| + outputMap[key] = recurseMap(value);
|
| + } else {
|
| + outputMap[key] = value.toMap();
|
| }
|
| });
|
| - return annotations;
|
| + return outputMap;
|
| }
|
|
|
| - /// Return an informative [Object.toString] for debugging.
|
| - String toString() => "${super.toString()}(${name.toString()})";
|
| + Map _filterMap(exported, map, test) {
|
| + map.forEach((key, value) {
|
| + if (test(key, value)) exported[key] = value;
|
| + });
|
| + return exported;
|
| + }
|
|
|
| - /// Return a map representation of this type.
|
| - Map toMap() {}
|
| + bool get _isVisible => _Generator._includePrivate || !isPrivate;
|
| }
|
|
|
| /// A class containing contents of a Dart library.
|
| @@ -890,36 +1031,125 @@ class Library extends Indexable {
|
| Map<String, Variable> variables;
|
|
|
| /// Top-level functions in the library.
|
| - MethodGroup functions;
|
| + Map<String, Method> functions;
|
|
|
| - /// Classes defined within the library
|
| - ClassGroup classes;
|
| + Map<String, Class> classes = {};
|
| + Map<String, Typedef> typedefs = {};
|
| + Map<String, Class> errors = {};
|
|
|
| String packageName = '';
|
| bool hasBeenCheckedForPackage = false;
|
| String packageIntro;
|
|
|
| - Map<String, Exported> _exportedMembers;
|
| + /// Returns the [Library] for the given [mirror] if it has already been
|
| + /// created, else creates it.
|
| + factory Library(LibraryMirror mirror) {
|
| + var library = getDocgenObject(mirror);
|
| + if (library is DummyMirror) {
|
| + library = new Library._(mirror);
|
| + }
|
| + return library;
|
| + }
|
|
|
| - Library(LibraryMirror libraryMirror) : super(libraryMirror) {
|
| + Library._(LibraryMirror libraryMirror) : super(libraryMirror) {
|
| var exported = _calcExportedItems(libraryMirror);
|
| - _createClasses(exported['classes']..addAll(libraryMirror.classes));
|
| - this.functions = _createMethods(
|
| - exported['methods']..addAll(libraryMirror.functions), this);
|
| - this.variables = _createVariables(
|
| - exported['variables']..addAll(libraryMirror.variables), this);
|
| -
|
| - var exportedVariables = {};
|
| - variables.forEach((key, value) {
|
| - if (value is ExportedVariable) {
|
| - exportedVariables[key] = value;
|
| - }
|
| + var exportedClasses = exported['classes']..addAll(libraryMirror.classes);
|
| + _findPackage(mirror);
|
| + classes = {};
|
| + typedefs = {};
|
| + errors = {};
|
| + exportedClasses.forEach((String mirrorName, ClassMirror classMirror) {
|
| + if (classMirror.isTypedef) {
|
| + // This is actually a Dart2jsTypedefMirror, and it does define value,
|
| + // but we don't have visibility to that type.
|
| + var mirror = classMirror;
|
| + if (_Generator._includePrivate || !mirror.isPrivate) {
|
| + entityMap[getDocgenObject(mirror).docName] =
|
| + new Typedef(mirror, this);
|
| + typedefs[mirror.simpleName] =
|
| + entityMap[getDocgenObject(mirror).docName];
|
| + }
|
| + } else {
|
| + var clazz = new Class(classMirror, this);
|
| +
|
| + if (clazz.isError()) {
|
| + errors[classMirror.simpleName] = clazz;
|
| + } else if (classMirror.isClass) {
|
| + classes[classMirror.simpleName] = clazz;
|
| + } else {
|
| + throw new ArgumentError(
|
| + '${classMirror.simpleName} - no class type match. ');
|
| + }
|
| + }
|
| });
|
| - _exportedMembers = new Map.from(this.classes.exported)
|
| - ..addAll(this.functions.exported)
|
| - ..addAll(exportedVariables);
|
| + this.functions = _createMethods(exported['methods']
|
| + ..addAll(libraryMirror.functions), this);
|
| + this.variables = _createVariables(exported['variables']
|
| + ..addAll(libraryMirror.variables), this);
|
| + }
|
| +
|
| + /// Look for the specified name starting with the current member, and
|
| + /// progressively working outward to the current library scope.
|
| + String findElementInScope(String name) {
|
| + var lookupFunc = Indexable.determineLookupFunc(name);
|
| + var libraryScope = lookupFunc(mirror, name);
|
| + if (libraryScope != null) {
|
| + var result = getDocgenObject(libraryScope, this);
|
| + if (result is DummyMirror) return packagePrefix + result.docName;
|
| + return result.packagePrefix + result.docName;
|
| + }
|
| + return super.findElementInScope(name);
|
| }
|
|
|
| + /// For a library's [mirror], determine the name of the package (if any) we
|
| + /// believe it came from (because of its file URI).
|
| + ///
|
| + /// If no package could be determined, we return an empty string.
|
| + String _findPackage(LibraryMirror mirror) {
|
| + if (mirror == null) return '';
|
| + if (hasBeenCheckedForPackage) return packageName;
|
| + hasBeenCheckedForPackage = true;
|
| + if (mirror.uri.scheme != 'file') return '';
|
| + var filePath = mirror.uri.toFilePath();
|
| + // We assume that we are documenting only libraries under package/lib
|
| + var rootdir = path.dirname((path.dirname(filePath)));
|
| + var pubspec = path.join(rootdir, 'pubspec.yaml');
|
| + packageName = _packageName(pubspec);
|
| + // Associate the package readme with all the libraries. This is a bit
|
| + // wasteful, but easier than trying to figure out which partial match
|
| + // is best.
|
| + packageIntro = _packageIntro(rootdir);
|
| + return packageName;
|
| + }
|
| +
|
| + String _packageIntro(packageDir) {
|
| + var dir = new Directory(packageDir);
|
| + var files = dir.listSync();
|
| + var readmes = files.where((FileSystemEntity each) => (each is File &&
|
| + each.path.substring(packageDir.length + 1, each.path.length)
|
| + .startsWith('README'))).toList();
|
| + if (readmes.isEmpty) return '';
|
| + // If there are multiples, pick the shortest name.
|
| + readmes.sort((a, b) => a.path.length.compareTo(b.path.length));
|
| + var readme = readmes.first;
|
| + var linkResolver = (name) => Indexable.globalFixReference(name);
|
| + var contents = markdown.markdownToHtml(readme
|
| + .readAsStringSync(), linkResolver: linkResolver,
|
| + inlineSyntaxes: _MARKDOWN_SYNTAXES);
|
| + return contents;
|
| + }
|
| +
|
| + /// Read a pubspec and return the library name.
|
| + String _packageName(String pubspecName) {
|
| + File pubspec = new File(pubspecName);
|
| + if (!pubspec.existsSync()) return '';
|
| + var contents = pubspec.readAsStringSync();
|
| + var spec = loadYaml(contents);
|
| + return spec["name"];
|
| + }
|
| +
|
| + markdown.Node fixReferenceWithScope(String name) => fixReference(name);
|
| +
|
| String get packagePrefix => packageName == null || packageName.isEmpty ?
|
| '' : '$packageName/';
|
|
|
| @@ -932,17 +1162,9 @@ class Library extends Indexable {
|
| return basic;
|
| }
|
|
|
| - String get owner => '';
|
| -
|
| - String get name => docName(mirror);
|
| + String get name => docName;
|
|
|
| - /// Set our classes field with error, typedef and regular classes.
|
| - void _createClasses(Map<String, ClassMirror> mirrorMap) {
|
| - this.classes = new ClassGroup();
|
| - mirrorMap.forEach((String mirrorName, ClassMirror mirror) {
|
| - this.classes.addClass(mirror, this);
|
| - });
|
| - }
|
| + String get docName => mirror.qualifiedName.replaceAll('.','-');
|
|
|
| /// For the given library determine what items (if any) are exported.
|
| ///
|
| @@ -1006,14 +1228,25 @@ class Library extends Indexable {
|
| return exports;
|
| }
|
|
|
| + /// Checks if the given name is a key for any of the Class Maps.
|
| + bool containsKey(String name) {
|
| + return classes.containsKey(name) || errors.containsKey(name);
|
| + }
|
| +
|
| /// Generates a map describing the [Library] object.
|
| Map toMap() => {
|
| 'name': name,
|
| 'qualifiedName': qualifiedName,
|
| 'comment': comment,
|
| 'variables': recurseMap(variables),
|
| - 'functions': functions.toMap(),
|
| - 'classes': classes.toMap(),
|
| + 'functions': expandMethodMap(functions),
|
| + 'classes': {
|
| + 'class': classes.values.where((c) => c._isVisible)
|
| + .map((e) => e.previewMap).toList(),
|
| + 'typedef': recurseMap(typedefs),
|
| + 'error': errors.values.where((e) => e._isVisible)
|
| + .map((e) => e.previewMap).toList()
|
| + },
|
| 'packageName': packageName,
|
| 'packageIntro' : packageIntro
|
| };
|
| @@ -1034,13 +1267,12 @@ class Class extends Indexable implements Comparable {
|
| Map<String, Variable> variables;
|
|
|
| /// Inherited variables in the class.
|
| - Map<String, Variable> inheritedVariables = {};
|
| + Map<String, Variable> inheritedVariables;
|
|
|
| /// Methods in the class.
|
| - MethodGroup methods;
|
| + Map<String, Method> methods;
|
|
|
| - /// Inherited methods in the class.
|
| - MethodGroup inheritedMethods = new MethodGroup();
|
| + Map<String, Method> inheritedMethods;
|
|
|
| /// Generic infomation about the class.
|
| Map<String, Generic> generics;
|
| @@ -1054,33 +1286,58 @@ class Class extends Indexable implements Comparable {
|
| /// Make sure that we don't check for inherited comments more than once.
|
| bool _commentsEnsured = false;
|
|
|
| + Indexable owner;
|
| +
|
| /// Returns the [Class] for the given [mirror] if it has already been created,
|
| /// else creates it.
|
| - factory Class(ClassMirror mirror) {
|
| - var clazz = entityMap[docName(mirror)];
|
| - if (clazz == null) {
|
| - clazz = new Class._(mirror);
|
| - entityMap[docName(mirror)] = clazz;
|
| + factory Class(ClassMirror mirror, Library owner) {
|
| + var clazz = getDocgenObject(mirror, owner);
|
| + if (clazz is DummyMirror) {
|
| + clazz = new Class._(mirror, owner);
|
| + entityMap[clazz.docName] = clazz;
|
| }
|
| return clazz;
|
| }
|
|
|
| - Class._(ClassMirror classMirror) : super(classMirror) {
|
| - var superclass = classMirror.superclass != null ?
|
| - new Class(classMirror.superclass) : null;
|
| - var interfaces = classMirror.superinterfaces.map(
|
| - (interface) => new Class(interface));
|
| + /// Called when we are constructing a superclass or interface class, but it
|
| + /// is not known if it belongs to the same owner as the original class. In
|
| + /// this case, we create an object whose owner is what the original mirror
|
| + /// says it is.
|
| + factory Class._possiblyDifferentOwner(ClassMirror mirror,
|
| + Library originalOwner) {
|
| + if (mirror.owner is LibraryMirror) {
|
| + var realOwner = getDocgenObject(mirror.owner);
|
| + if (realOwner is Library) {
|
| + return new Class(mirror, realOwner);
|
| + } else {
|
| + return new Class(mirror, originalOwner);
|
| + }
|
| + } else {
|
| + return new Class(mirror, originalOwner);
|
| + }
|
| + }
|
| +
|
| + Class._(ClassMirror classMirror, this.owner) : super(classMirror) {
|
| + inheritedVariables = {};
|
|
|
| - this.superclass = superclass;
|
| - this.interfaces = interfaces.toList();
|
| - this.variables = _createVariables(classMirror.variables);
|
| - this.methods = _createMethods(classMirror.methods);
|
| - this.annotations = _createAnnotations(classMirror);
|
| - this.generics = _createGenerics(classMirror);
|
| - this.isAbstract = classMirror.isAbstract;
|
| + // The reason we do this madness is the superclass and interface owners may
|
| + // not be this class's owner!! Example: BaseClient in http pkg.
|
| + var superinterfaces = classMirror.superinterfaces.map(
|
| + (interface) => new Class._possiblyDifferentOwner(interface, owner));
|
| + this.superclass = classMirror.superclass == null? null :
|
| + new Class._possiblyDifferentOwner(classMirror.superclass, owner);
|
|
|
| - // Tell all superclasses that you are a subclass.
|
| - if (!classMirror.isNameSynthetic && _isVisible(this)) {
|
| + interfaces = superinterfaces.toList();
|
| + variables = _createVariables(classMirror.variables, this);
|
| + methods = _createMethods(classMirror.methods, this);
|
| + annotations = _createAnnotations(classMirror, this);
|
| + generics = _createGenerics(classMirror);
|
| + isAbstract = classMirror.isAbstract;
|
| + inheritedMethods = new Map<String, Method>();
|
| +
|
| + // Tell all superclasses that you are a subclass, unless you are not
|
| + // visible or an intermediary mixin class.
|
| + if (!classMirror.isNameSynthetic && _isVisible) {
|
| parentChain().forEach((parentClass) {
|
| parentClass.addSubclass(this);
|
| });
|
| @@ -1090,6 +1347,35 @@ class Class extends Indexable implements Comparable {
|
| interfaces.forEach((interface) => addInherited(interface));
|
| }
|
|
|
| + String get packagePrefix => owner.packagePrefix;
|
| +
|
| + String _lookupInClassAndSuperclasses(String name) {
|
| + var lookupFunc = Indexable.determineLookupFunc(name);
|
| + var classScope = this;
|
| + while (classScope != null) {
|
| + var classFunc = lookupFunc(classScope.mirror, name);
|
| + if (classFunc != null) {
|
| + return packagePrefix + getDocgenObject(classFunc, owner).docName;
|
| + }
|
| + classScope = classScope.superclass;
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + /// Look for the specified name starting with the current member, and
|
| + /// progressively working outward to the current library scope.
|
| + String findElementInScope(String name) {
|
| + var lookupFunc = Indexable.determineLookupFunc(name);
|
| + var result = _lookupInClassAndSuperclasses(name);
|
| + if (result != null) {
|
| + return result;
|
| + }
|
| + result = owner.findElementInScope(name);
|
| + return result == null ? super.findElementInScope(name) : result;
|
| + }
|
| +
|
| + markdown.Node fixReferenceWithScope(String name) => fixReference(name);
|
| +
|
| String get typeName => 'class';
|
|
|
| /// Returns a list of all the parent classes.
|
| @@ -1104,16 +1390,46 @@ class Class extends Indexable implements Comparable {
|
| /// the superclass.
|
| void addInherited(Class superclass) {
|
| inheritedVariables.addAll(superclass.inheritedVariables);
|
| - inheritedVariables.addAll(_filterStatics(superclass.variables));
|
| - inheritedMethods.addInherited(superclass);
|
| + inheritedVariables.addAll(_allButStatics(superclass.variables));
|
| + addInheritedMethod(superclass, this);
|
| + }
|
| +
|
| + /** [newParent] refers to the actual class is currently using these methods.
|
| + * which may be different because with the mirror system, we only point to the
|
| + * original canonical superclasse's method.
|
| + */
|
| + void addInheritedMethod(Class parent, Class newParent) {
|
| + parent.inheritedMethods.forEach((name, method) {
|
| + if(!method.isConstructor){
|
| + inheritedMethods[name] = new Method(method.mirror, newParent, method);
|
| + }}
|
| + );
|
| + _allButStatics(parent.methods).forEach((name, method) {
|
| + if (!method.isConstructor) {
|
| + inheritedMethods[name] = new Method(method.mirror, newParent, method);
|
| + }}
|
| + );
|
| + }
|
| +
|
| + /// Remove statics from the map of inherited items before adding them.
|
| + Map _allButStatics(Map items) {
|
| + var result = {};
|
| + items.forEach((name, item) {
|
| + if (!item.isStatic) {
|
| + result[name] = item;
|
| + }
|
| + });
|
| + return result;
|
| }
|
|
|
| /// Add the subclass to the class.
|
| ///
|
| - /// If [this] is private, it will add the subclass to the list of subclasses
|
| - /// in the superclasses.
|
| + /// If [this] is private (or an intermediary mixin class), it will add the
|
| + /// subclass to the list of subclasses in the superclasses.
|
| void addSubclass(Class subclass) {
|
| - if (!_includePrivate && isPrivate) {
|
| + if (docName == 'dart-core.Object') return;
|
| +
|
| + if (!_Generator._includePrivate && isPrivate || mirror.isNameSynthetic) {
|
| if (superclass != null) superclass.addSubclass(subclass);
|
| interfaces.forEach((interface) {
|
| interface.addSubclass(subclass);
|
| @@ -1135,40 +1451,23 @@ class Class extends Indexable implements Comparable {
|
| return superclass.isError();
|
| }
|
|
|
| - /// Check that the class exists in the owner library.
|
| - ///
|
| - /// If it does not exist in the owner library, it is a mixin applciation and
|
| - /// should be removed.
|
| - void updateLinksAndRemoveIntermediaryClasses() {
|
| - var library = entityMap[owner];
|
| - if (library != null) {
|
| - if (!library.classes.containsKey(name) && mirror.isNameSynthetic) {
|
| - // In the mixin case, remove the intermediary classes.
|
| - this.isPrivate = true;
|
| - // Since we are now making the mixin a private class, make all elements
|
| - // with the mixin as an owner private too.
|
| - entityMap.values.where((e) => e.owner == qualifiedName).forEach(
|
| - (element) => element.isPrivate = true);
|
| - // Move the subclass up to the next public superclass
|
| - subclasses.forEach((subclass) => addSubclass(subclass));
|
| - } else {
|
| - // It is an exported item. Loop through each of the exported types,
|
| - // and tell them to update their links, given these other exported
|
| - // names within the library.
|
| - for (Exported member in library._exportedMembers.values) {
|
| - member.updateExports(library._exportedMembers);
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| /// Makes sure that all methods with inherited equivalents have comments.
|
| void ensureComments() {
|
| if (_commentsEnsured) return;
|
| _commentsEnsured = true;
|
| + if (superclass != null) superclass.ensureComments();
|
| inheritedMethods.forEach((qualifiedName, inheritedMethod) {
|
| var method = methods[qualifiedName];
|
| - if (method != null) method.ensureCommentFor(inheritedMethod);
|
| + if (method != null) {
|
| + // if we have overwritten this method in this class, we still provide
|
| + // the opportunity to inherit the comments.
|
| + method.ensureCommentFor(inheritedMethod);
|
| + }
|
| + });
|
| + // we need to populate the comments for all methods. so that the subclasses
|
| + // can get for their inherited versions the comments.
|
| + methods.forEach((qualifiedName, method) {
|
| + if (!method.mirror.isConstructor) method.ensureCommentFor(method);
|
| });
|
| }
|
|
|
| @@ -1176,7 +1475,7 @@ class Class extends Indexable implements Comparable {
|
| /// superclass of the private superclass.
|
| String validSuperclass() {
|
| if (superclass == null) return 'dart.core.Object';
|
| - if (_isVisible(superclass)) return superclass.qualifiedName;
|
| + if (superclass._isVisible) return superclass.qualifiedName;
|
| return superclass.validSuperclass();
|
| }
|
|
|
| @@ -1187,14 +1486,14 @@ class Class extends Indexable implements Comparable {
|
| 'comment': comment,
|
| 'isAbstract' : isAbstract,
|
| 'superclass': validSuperclass(),
|
| - 'implements': interfaces.where(_isVisible)
|
| + 'implements': interfaces.where((i) => i._isVisible)
|
| .map((e) => e.qualifiedName).toList(),
|
| 'subclass': (subclasses.toList()..sort())
|
| .map((x) => x.qualifiedName).toList(),
|
| 'variables': recurseMap(variables),
|
| 'inheritedVariables': recurseMap(inheritedVariables),
|
| - 'methods': methods.toMap(),
|
| - 'inheritedMethods': inheritedMethods.toMap(),
|
| + 'methods': expandMethodMap(methods),
|
| + 'inheritedMethods': expandMethodMap(inheritedMethods),
|
| 'annotations': annotations.map((a) => a.toMap()).toList(),
|
| 'generics': recurseMap(generics)
|
| };
|
| @@ -1202,113 +1501,6 @@ class Class extends Indexable implements Comparable {
|
| int compareTo(aClass) => name.compareTo(aClass.name);
|
| }
|
|
|
| -abstract class Exported {
|
| - void updateExports(Map<String, Indexable> libraryExports);
|
| -}
|
| -
|
| -Map _filterMap(exported, map, test) {
|
| - map.forEach((key, value) {
|
| - if (test(value)) exported[key] = value;
|
| - });
|
| - return exported;
|
| -}
|
| -
|
| -class ExportedClass extends Class implements Exported {
|
| - Class _originalClass;
|
| - Library _exportingLibrary;
|
| -
|
| - ExportedClass(ClassMirror originalClass, Library this._exportingLibrary) :
|
| - super._(originalClass) {
|
| - _originalClass = new Class(originalClass);
|
| - }
|
| -
|
| - // The qualified name (for URL purposes) and the file name are the same,
|
| - // of the form packageName/ClassName or packageName/ClassName.methodName.
|
| - // This defines both the URL and the directory structure.
|
| - String get fileName => path.join(_exportingLibrary.packageName,
|
| - _exportingLibrary.mirror.qualifiedName + '.' + _originalClass.name);
|
| -
|
| - void updateExports(Map<String, Indexable> libraryExports) {
|
| - // TODO(efortuna): If this class points to another exported class or type
|
| - // of some sort, then that reference needs to be updated here.
|
| - /* these need to be updated:
|
| - 'comment': comment,
|
| - 'superclass': validSuperclass(),
|
| - 'implements': interfaces.where(_isVisible)
|
| - .map((e) => e.qualifiedName).toList(),
|
| - 'subclass': (subclasses.toList()..sort())
|
| - .map((x) => x.qualifiedName).toList(),
|
| - 'variables': recurseMap(variables),
|
| - 'inheritedVariables': recurseMap(inheritedVariables),
|
| - 'methods': methods.toMap(),
|
| - 'inheritedMethods': inheritedMethods.toMap(),
|
| - 'annotations': annotations.map((a) => a.toMap()).toList(),
|
| - 'generics': recurseMap(generics)
|
| - */
|
| - }
|
| -}
|
| -
|
| -/// A container to categorize classes into the following groups: abstract
|
| -/// classes, regular classes, typedefs, and errors.
|
| -class ClassGroup {
|
| - Map<String, Class> classes = {};
|
| - Map<String, Typedef> typedefs = {};
|
| - Map<String, Class> errors = {};
|
| -
|
| - Map<String, Exported> get exported {
|
| - var exported = _filterMap({}, classes, (value) => value is ExportedClass);
|
| - // TODO(efortuna): The line below needs updating.
|
| - exported = _filterMap(exported, typedefs,
|
| - (value) => value is ExportedClass);
|
| - exported = _filterMap(exported, errors,
|
| - (value) => value is ExportedClass);
|
| - return exported;
|
| - }
|
| -
|
| - void addClass(ClassMirror classMirror, Library containingLibrary) {
|
| - if (classMirror.isTypedef) {
|
| - // This is actually a Dart2jsTypedefMirror, and it does define value,
|
| - // but we don't have visibility to that type.
|
| - var mirror = classMirror;
|
| - if (_includePrivate || !mirror.isPrivate) {
|
| - entityMap[docName(mirror)] = new Typedef(mirror);
|
| - typedefs[mirror.simpleName] = entityMap[docName(mirror)];
|
| - }
|
| - } else {
|
| - var clazz = new Class(classMirror);
|
| -
|
| - if (classMirror.library.qualifiedName !=
|
| - containingLibrary.mirror.qualifiedName) {
|
| - var exportedClass = new ExportedClass(classMirror, containingLibrary);
|
| - entityMap[clazz.fileName] = exportedClass;
|
| - clazz = exportedClass;
|
| - }
|
| -
|
| - if (clazz.isError()) {
|
| - errors[classMirror.simpleName] = clazz;
|
| - } else if (classMirror.isClass) {
|
| - classes[classMirror.simpleName] = clazz;
|
| - } else {
|
| - throw new ArgumentError(
|
| - '${classMirror.simpleName} - no class type match. ');
|
| - }
|
| - }
|
| - }
|
| -
|
| - /// Checks if the given name is a key for any of the Class Maps.
|
| - bool containsKey(String name) {
|
| - return classes.containsKey(name) || errors.containsKey(name);
|
| - }
|
| -
|
| - Map toMap() => {
|
| - 'class': classes.values.where(_isVisible)
|
| - .map((e) => e.previewMap).toList(),
|
| - 'typedef': recurseMap(typedefs),
|
| - 'error': errors.values.where(_isVisible)
|
| - .map((e) => e.previewMap).toList()
|
| - };
|
| -}
|
| -
|
| class Typedef extends Indexable {
|
| String returnType;
|
|
|
| @@ -1320,11 +1512,22 @@ class Typedef extends Indexable {
|
| /// List of the meta annotations on the typedef.
|
| List<Annotation> annotations;
|
|
|
| - Typedef(mirror) : super(mirror) {
|
| - this.returnType = docName(mirror.value.returnType);
|
| - this.generics = _createGenerics(mirror);
|
| - this.parameters = _createParameters(mirror.value.parameters);
|
| - this.annotations = _createAnnotations(mirror);
|
| + /// Returns the [Library] for the given [mirror] if it has already been
|
| + /// created, else creates it.
|
| + factory Typedef(TypedefMirror mirror, Library owningLibrary) {
|
| + var aTypedef = getDocgenObject(mirror, owningLibrary);
|
| + if (aTypedef is DummyMirror) {
|
| + aTypedef = new Typedef._(mirror, owningLibrary);
|
| + }
|
| + return aTypedef;
|
| + }
|
| +
|
| + Typedef._(TypedefMirror mirror, Library owningLibrary) : super(mirror) {
|
| + owner = owningLibrary;
|
| + returnType = getDocgenObject(mirror.value.returnType).docName;
|
| + generics = _createGenerics(mirror);
|
| + parameters = _createParameters(mirror.value.parameters);
|
| + annotations = _createAnnotations(mirror, this);
|
| }
|
|
|
| Map toMap() => {
|
| @@ -1348,16 +1551,27 @@ class Variable extends Indexable {
|
| bool isConst;
|
| Type type;
|
| String _variableName;
|
| + Indexable owner;
|
|
|
| /// List of the meta annotations on the variable.
|
| List<Annotation> annotations;
|
|
|
| - Variable(this._variableName, VariableMirror mirror) : super(mirror) {
|
| - this.isFinal = mirror.isFinal;
|
| - this.isStatic = mirror.isStatic;
|
| - this.isConst = mirror.isConst;
|
| - this.type = _createType(mirror.type);
|
| - this.annotations = _createAnnotations(mirror);
|
| + factory Variable(String variableName, VariableMirror mirror,
|
| + Indexable owner) {
|
| + var variable = getDocgenObject(mirror);
|
| + if (variable is DummyMirror) {
|
| + return new Variable._(variableName, mirror, owner);
|
| + }
|
| + return variable;
|
| + }
|
| +
|
| + Variable._(this._variableName, VariableMirror mirror, this.owner) :
|
| + super(mirror) {
|
| + isFinal = mirror.isFinal;
|
| + isStatic = mirror.isStatic;
|
| + isConst = mirror.isConst;
|
| + type = new Type(mirror.type, _getOwningLibrary(owner));
|
| + annotations = _createAnnotations(mirror, _getOwningLibrary(owner));
|
| }
|
|
|
| String get name => _variableName;
|
| @@ -1374,35 +1588,36 @@ class Variable extends Indexable {
|
| 'annotations': annotations.map((a) => a.toMap()).toList()
|
| };
|
|
|
| + String get packagePrefix => owner.packagePrefix;
|
| +
|
| String get typeName => 'property';
|
|
|
| get comment {
|
| if (_comment != null) return _comment;
|
| - var owningClass = owningEntity;
|
| - if (owningClass is Class) {
|
| - owningClass.ensureComments();
|
| + if (owner is Class) {
|
| + (owner as Class).ensureComments();
|
| }
|
| return super.comment;
|
| }
|
| -}
|
|
|
| -class ExportedVariable extends Variable implements Exported {
|
| - Library _exportingLibrary;
|
| + markdown.Node fixReferenceWithScope(String name) => fixReference(name);
|
|
|
| - ExportedVariable(String variableName, VariableMirror originalVariable,
|
| - Library this._exportingLibrary) : super(variableName, originalVariable);
|
| -
|
| - String get fileName => path.join(_exportingLibrary.packageName,
|
| - _exportingLibrary.mirror.qualifiedName + '.' + super.name);
|
| + String findElementInScope(String name) {
|
| + var lookupFunc = Indexable.determineLookupFunc(name);
|
| + var result = lookupFunc(mirror, name);
|
| + if (result != null) {
|
| + result = getDocgenObject(result);
|
| + if (result is DummyMirror) return packagePrefix + result.docName;
|
| + return result.packagePrefix + result.docName;
|
| + }
|
|
|
| - void updateExports(Map<String, Indexable> libraryExports) {
|
| - // TODO(efortuna): if this class points to another exported class or type
|
| - // of some sort, then that reference needs to be updated here.
|
| - /* these need to be updated:
|
| - 'comment': comment,
|
| - 'type': new List.filled(1, type.toMap()),
|
| - 'annotations': annotations.map((a) => a.toMap()).toList()
|
| - */
|
| + if (owner != null) {
|
| + var result = owner.findElementInScope(name);
|
| + if (result != null) {
|
| + return result;
|
| + }
|
| + }
|
| + return super.findElementInScope(name);
|
| }
|
| }
|
|
|
| @@ -1420,6 +1635,7 @@ class Method extends Indexable {
|
| bool isSetter;
|
| bool isOperator;
|
| Type returnType;
|
| + Method methodInheritedFrom;
|
|
|
| /// Qualified name to state where the comment is inherited from.
|
| String commentInheritedFrom = "";
|
| @@ -1427,23 +1643,78 @@ class Method extends Indexable {
|
| /// List of the meta annotations on the method.
|
| List<Annotation> annotations;
|
|
|
| - Method(MethodMirror mirror) : super(mirror) {
|
| + Indexable owner;
|
| +
|
| + factory Method(MethodMirror mirror, Indexable owner,
|
| + [Method methodInheritedFrom]) {
|
| + var method = getDocgenObject(mirror, owner);
|
| + if (method is DummyMirror) {
|
| + method = new Method._(mirror, owner, methodInheritedFrom);
|
| + }
|
| + return method;
|
| + }
|
| +
|
| + Method._(MethodMirror mirror, this.owner, this.methodInheritedFrom)
|
| + : super(mirror) {
|
| this.isStatic = mirror.isStatic;
|
| this.isAbstract = mirror.isAbstract;
|
| this.isConst = mirror.isConstConstructor;
|
| - this.returnType = _createType(mirror.returnType);
|
| - this.parameters = _createParameters(mirror.parameters);
|
| - this.annotations = _createAnnotations(mirror);
|
| + this.returnType = new Type(mirror.returnType, _getOwningLibrary(owner));
|
| + this.parameters = _createParameters(mirror.parameters, owner);
|
| + this.annotations = _createAnnotations(mirror, _getOwningLibrary(owner));
|
| this.isConstructor = mirror.isConstructor;
|
| this.isGetter = mirror.isGetter;
|
| this.isSetter = mirror.isSetter;
|
| this.isOperator = mirror.isOperator;
|
| }
|
|
|
| + String get packagePrefix => owner.packagePrefix;
|
| +
|
| + markdown.Node fixReferenceWithScope(String name) => fixReference(name);
|
| +
|
| + /// Look for the specified name starting with the current member, and
|
| + /// progressively working outward to the current library scope.
|
| + String findElementInScope(String name) {
|
| + var lookupFunc = Indexable.determineLookupFunc(name);
|
| +
|
| + var memberScope = lookupFunc(this.mirror, name);
|
| + if (memberScope != null) {
|
| + // do we check for a dummy mirror returned here and look up with an owner
|
| + // higher ooooor in getDocgenObject do we include more things in our
|
| + // lookup
|
| + var result = getDocgenObject(memberScope, owner);
|
| + if (result is DummyMirror && owner.owner != null
|
| + && owner.owner is! DummyMirror) {
|
| + var aresult = getDocgenObject(memberScope, owner.owner);
|
| + if (aresult is! DummyMirror) result = aresult;
|
| + }
|
| + if (result is DummyMirror) return packagePrefix + result.docName;
|
| + return result.packagePrefix + result.docName;
|
| + }
|
| +
|
| + if (owner != null) {
|
| + var result = owner.findElementInScope(name);
|
| + if (result != null) return result;
|
| + }
|
| + return super.findElementInScope(name);
|
| + }
|
| +
|
| + String get docName {
|
| + if ((mirror as MethodMirror).isConstructor) {
|
| + // We name constructors specially -- including the class name again and a
|
| + // "-" to separate the constructor from its name (if any).
|
| + return '${mirror.owner.simpleName.replaceAll(".", "_")}.'
|
| + '${mirror.owner.simpleName}-${mirror.simpleName}';
|
| + }
|
| + return super.docName;
|
| + }
|
| +
|
| /// Makes sure that the method with an inherited equivalent have comments.
|
| void ensureCommentFor(Method inheritedMethod) {
|
| if (comment.isNotEmpty) return;
|
| - comment = inheritedMethod._commentToHtml(mirror);
|
| +
|
| + comment = inheritedMethod._commentToHtml(this);
|
| + _unresolvedComment = inheritedMethod._unresolvedComment;
|
| commentInheritedFrom = inheritedMethod.commentInheritedFrom == '' ?
|
| inheritedMethod.qualifiedName : inheritedMethod.commentInheritedFrom;
|
| }
|
| @@ -1453,7 +1724,11 @@ class Method extends Indexable {
|
| 'name': name,
|
| 'qualifiedName': qualifiedName,
|
| 'comment': comment,
|
| - 'commentFrom': commentInheritedFrom,
|
| + 'commentFrom': (methodInheritedFrom != null &&
|
| + commentInheritedFrom == methodInheritedFrom.docName ? ''
|
| + : commentInheritedFrom),
|
| + 'inheritedFrom': (methodInheritedFrom == null? '' :
|
| + methodInheritedFrom.docName),
|
| 'static': isStatic.toString(),
|
| 'abstract': isAbstract.toString(),
|
| 'constant': isConst.toString(),
|
| @@ -1468,127 +1743,30 @@ class Method extends Indexable {
|
|
|
| get comment {
|
| if (_comment != null) return _comment;
|
| - var owningClass = owningEntity;
|
| - if (owningClass is Class) {
|
| - owningClass.ensureComments();
|
| + if (owner is Class) {
|
| + (owner as Class).ensureComments();
|
| }
|
| - return super.comment;
|
| - }
|
| -}
|
| -
|
| -class ExportedMethod extends Method implements Exported {
|
| - Library _exportingLibrary;
|
| -
|
| - ExportedMethod(MethodMirror originalMethod, Library this._exportingLibrary) :
|
| - super(originalMethod);
|
| -
|
| - // TODO(efortuna): Refactor this code so the exported items can share this
|
| - // behavior.
|
| - String get fileName => path.join(_exportingLibrary.packageName,
|
| - _exportingLibrary.mirror.qualifiedName + '.' + super.name);
|
| -
|
| - void updateExports(Map<String, Indexable> libraryExports) {
|
| - // TODO(efortuna): if this class points to another exported class or type
|
| - // of some sort, then that reference needs to be updated here.
|
| - /* these need to be updated:
|
| - 'qualifiedName': qualifiedName,
|
| - 'comment': comment,
|
| - 'commentFrom': commentInheritedFrom,
|
| - 'return': new List.filled(1, returnType.toMap()),
|
| - 'parameters': recurseMap(parameters),
|
| - 'annotations': annotations.map((a) => a.toMap()).toList()
|
| - */
|
| - }
|
| -}
|
| -
|
| -
|
| -
|
| -/// A container to categorize methods into the following groups: setters,
|
| -/// getters, constructors, operators, regular methods.
|
| -class MethodGroup {
|
| - Map<String, Method> setters = {};
|
| - Map<String, Method> getters = {};
|
| - Map<String, Method> constructors = {};
|
| - Map<String, Method> operators = {};
|
| - Map<String, Method> regularMethods = {};
|
| -
|
| - Map<String, Exported> get exported {
|
| - var exported = {};
|
| - for (Map<String, Method> group in [setters, getters, constructors,
|
| - operators, regularMethods]) {
|
| - exported = _filterMap(exported, group,
|
| - (value) => value is ExportedMethod);
|
| - }
|
| - return exported;
|
| - }
|
| -
|
| - /// The optional parameter [containingLibrary] is contains data for variables
|
| - /// defined at the top level of a library (potentially for exporting
|
| - /// purposes).
|
| - void addMethod(MethodMirror mirror, [Library containingLibrary]) {
|
| - var method;
|
| - if (containingLibrary != null && mirror.owner.qualifiedName !=
|
| - containingLibrary.mirror.qualifiedName) {
|
| - method = new ExportedMethod(mirror, containingLibrary);
|
| - } else {
|
| - method = new Method(mirror);
|
| - }
|
| - entityMap[docName(mirror)] = method;
|
| - if (mirror.isSetter) {
|
| - setters[mirror.simpleName] = method;
|
| - } else if (mirror.isGetter) {
|
| - getters[mirror.simpleName] = method;
|
| - } else if (mirror.isConstructor) {
|
| - constructors[mirror.simpleName] = method;
|
| - } else if (mirror.isOperator) {
|
| - operators[mirror.simpleName] = method;
|
| - } else if (mirror.isRegularMethod) {
|
| - regularMethods[mirror.simpleName] = method;
|
| - } else {
|
| - throw new ArgumentError('${mirror.simpleName} - no method type match');
|
| + var result = super.comment;
|
| + if (result == '' && methodInheritedFrom != null) {
|
| + // this should be NOT from the MIRROR, but from the COMMENT
|
| + _unresolvedComment = methodInheritedFrom._unresolvedComment;
|
| +
|
| + var linkResolver = (name) => fixReferenceWithScope(name);
|
| + comment = _unresolvedComment == null ? '' :
|
| + markdown.markdownToHtml(_unresolvedComment.trim(),
|
| + linkResolver: linkResolver, inlineSyntaxes: _MARKDOWN_SYNTAXES);
|
| + commentInheritedFrom = methodInheritedFrom.commentInheritedFrom;
|
| + result = comment;
|
| + //print('result was $comment');
|
| }
|
| - }
|
| -
|
| - void addInherited(Class parent) {
|
| - setters.addAll(parent.inheritedMethods.setters);
|
| - setters.addAll(_filterStatics(parent.methods.setters));
|
| - getters.addAll(parent.inheritedMethods.getters);
|
| - getters.addAll(_filterStatics(parent.methods.getters));
|
| - operators.addAll(parent.inheritedMethods.operators);
|
| - operators.addAll(_filterStatics(parent.methods.operators));
|
| - regularMethods.addAll(parent.inheritedMethods.regularMethods);
|
| - regularMethods.addAll(_filterStatics(parent.methods.regularMethods));
|
| - }
|
| -
|
| - Map toMap() => {
|
| - 'setters': recurseMap(setters),
|
| - 'getters': recurseMap(getters),
|
| - 'constructors': recurseMap(constructors),
|
| - 'operators': recurseMap(operators),
|
| - 'methods': recurseMap(regularMethods)
|
| - };
|
| -
|
| - Method operator [](String qualifiedName) {
|
| - if (setters.containsKey(qualifiedName)) return setters[qualifiedName];
|
| - if (getters.containsKey(qualifiedName)) return getters[qualifiedName];
|
| - if (operators.containsKey(qualifiedName)) return operators[qualifiedName];
|
| - if (regularMethods.containsKey(qualifiedName)) {
|
| - return regularMethods[qualifiedName];
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - void forEach(void f(String key, Method value)) {
|
| - setters.forEach(f);
|
| - getters.forEach(f);
|
| - operators.forEach(f);
|
| - regularMethods.forEach(f);
|
| + return result;
|
| }
|
| }
|
|
|
| /// A class containing properties of a Dart method/function parameter.
|
| -class Parameter {
|
| +class Parameter extends MirrorBased {
|
|
|
| + ParameterMirror mirror;
|
| String name;
|
| bool isOptional;
|
| bool isNamed;
|
| @@ -1599,8 +1777,15 @@ class Parameter {
|
| /// List of the meta annotations on the parameter.
|
| List<Annotation> annotations;
|
|
|
| - Parameter(this.name, this.isOptional, this.isNamed, this.hasDefaultValue,
|
| - this.type, this.defaultValue, this.annotations);
|
| + Parameter(this.mirror, [Indexable owner]) {
|
| + name = mirror.simpleName;
|
| + isOptional = mirror.isOptional;
|
| + isNamed = mirror.isNamed;
|
| + hasDefaultValue = mirror.hasDefaultValue;
|
| + defaultValue = mirror.defaultValue;
|
| + type = new Type(mirror.type, owner);
|
| + annotations = _createAnnotations(mirror, this);
|
| + }
|
|
|
| /// Generates a map describing the [Parameter] object.
|
| Map toMap() => {
|
| @@ -1614,16 +1799,13 @@ class Parameter {
|
| };
|
| }
|
|
|
| -/// A class containing properties of a Generic.
|
| -class Generic {
|
| - String name;
|
| - String type;
|
| -
|
| - Generic(this.name, this.type);
|
| -
|
| +/// A Docgen wrapper around the dart2js mirror for a generic type.
|
| +class Generic extends MirrorBased {
|
| + TypeVariableMirror mirror;
|
| + Generic(this.mirror);
|
| Map toMap() => {
|
| - 'name': name,
|
| - 'type': type
|
| + 'name': mirror.toString(),
|
| + 'type': mirror.upperBound.qualifiedName
|
| };
|
| }
|
|
|
| @@ -1655,56 +1837,57 @@ class Generic {
|
| /// "inner" :
|
| /// - "outer" : "dart-core.int"
|
| /// "inner" :
|
| -class Type {
|
| - String outer;
|
| - List<Type> inner;
|
| +class Type extends MirrorBased {
|
| + TypeMirror mirror;
|
| + MirrorBased owner;
|
|
|
| - Type(this.outer, this.inner);
|
| + factory Type(TypeMirror mirror, [MirrorBased owner]) {
|
| + return new Type._(mirror, owner);
|
| + }
|
|
|
| - Map toMap() => {
|
| - 'outer': outer,
|
| - 'inner': inner.map((e) => e.toMap()).toList()
|
| - };
|
| + Type._(this.mirror, this.owner);
|
| +
|
| + /// Returns a list of [Type] objects constructed from TypeMirrors.
|
| + List<Type> _createTypeGenerics(TypeMirror mirror) {
|
| + if (mirror is ClassMirror && !mirror.isTypedef) {
|
| + var innerList = [];
|
| + mirror.typeArguments.forEach((e) {
|
| + innerList.add(new Type(e, owner));
|
| + });
|
| + return innerList;
|
| + }
|
| + return [];
|
| + }
|
| +
|
| + Map toMap() {
|
| + // We may encounter types whose corresponding library has not been
|
| + // processed yet, so look up the owner at the last moment.
|
| + var result = getDocgenObject(mirror, owner);
|
| + return {
|
| + 'outer': result.docName,
|
| + 'inner': _createTypeGenerics(mirror).map((e) => e.toMap()).toList(),
|
| + };
|
| + }
|
| }
|
|
|
| /// Holds the name of the annotation, and its parameters.
|
| -class Annotation {
|
| - String qualifiedName;
|
| +class Annotation extends MirrorBased {
|
| List<String> parameters;
|
| + /// The class of this annotation.
|
| + ClassMirror mirror;
|
|
|
| - Annotation(this.qualifiedName, this.parameters);
|
| + Annotation(InstanceMirror originalMirror, MirrorBased annotationOwner) {
|
| + mirror = originalMirror.type;
|
| + parameters = originalMirror.type.variables.values
|
| + .where((e) => e.isFinal)
|
| + .map((e) => originalMirror.getField(e.simpleName).reflectee)
|
| + .where((e) => e != null)
|
| + .toList();
|
| + owner = annotationOwner;
|
| + }
|
|
|
| Map toMap() => {
|
| - 'name': qualifiedName,
|
| + 'name': getDocgenObject(mirror, owner).docName,
|
| 'parameters': parameters
|
| };
|
| }
|
| -
|
| -/// Given a mirror, returns its qualified name, but following the conventions
|
| -/// we're using in Dartdoc, which is that library names with dots in them
|
| -/// have them replaced with hyphens.
|
| -String docName(DeclarationMirror m) {
|
| - if (m is LibraryMirror) {
|
| - return m.qualifiedName.replaceAll('.','-');
|
| - }
|
| - var owner = m.owner;
|
| - if (owner == null) return m.qualifiedName;
|
| - var simpleName = m.simpleName;
|
| - if (m is MethodMirror && m.isConstructor) {
|
| - // We name constructors specially -- including the class name again and a
|
| - // "-" to separate the constructor from its name (if any).
|
| - simpleName = '${owner.simpleName}-$simpleName';
|
| - }
|
| - return docName(owner) + '.' + simpleName;
|
| -}
|
| -
|
| -/// Remove statics from the map of inherited items before adding them.
|
| -Map _filterStatics(Map items) {
|
| - var result = {};
|
| - items.forEach((name, item) {
|
| - if (!item.isStatic) {
|
| - result[name] = item;
|
| - }
|
| - });
|
| - return result;
|
| -}
|
|
|