Index: sdk/lib/_internal/dartdoc/lib/dartdoc.dart |
diff --git a/sdk/lib/_internal/dartdoc/lib/dartdoc.dart b/sdk/lib/_internal/dartdoc/lib/dartdoc.dart |
index 86bf1d646b9ec6ea912551645b182555f3cf8202..7dfc0b00163e115bc5687d7f3ac3aaf830b58d2e 100644 |
--- a/sdk/lib/_internal/dartdoc/lib/dartdoc.dart |
+++ b/sdk/lib/_internal/dartdoc/lib/dartdoc.dart |
@@ -18,15 +18,20 @@ library dartdoc; |
import 'dart:async'; |
import 'dart:io'; |
+import 'dart:isolate'; |
import 'dart:json' as json; |
import 'dart:math'; |
import 'dart:uri'; |
+import 'package:pathos/path.dart' as pathos; |
+ |
import 'classify.dart'; |
import 'markdown.dart' as md; |
import 'universe_serializer.dart'; |
import 'src/dartdoc/nav.dart'; |
+import 'src/dartdoc/utils.dart'; |
+import 'src/export_map.dart'; |
import 'src/json_serializer.dart' as json_serializer; |
// TODO(rnystrom): Use "package:" URL (#4968). |
@@ -35,8 +40,6 @@ import '../../compiler/implementation/mirrors/mirrors.dart'; |
import '../../compiler/implementation/mirrors/mirrors_util.dart'; |
import '../../libraries.dart'; |
-part 'src/dartdoc/utils.dart'; |
- |
/** |
* Generates completely static HTML containing everything you need to browse |
* the docs. The only client side behavior is trivial stuff like syntax |
@@ -131,16 +134,38 @@ Future copyDirectory(Path from, Path to) { |
*/ |
Future compileScript(int mode, Path outputDir, Path libPath) { |
print('Compiling client JavaScript...'); |
- var clientScript = (mode == MODE_STATIC) ? 'static' : 'live-nav'; |
- var dartPath = libPath.append( |
- 'lib/_internal/dartdoc/lib/src/client/client-$clientScript.dart'); |
- var jsPath = outputDir.append('client-$clientScript.js'); |
- |
- return dart2js.compile(dartPath, libPath, |
- options: const <String>['--categories=Client,Server']) |
- .then((jsCode) { |
- writeString(new File.fromPath(jsPath), jsCode); |
+ |
+ // TODO(nweiz): don't run this in an isolate when issue 9815 is fixed. |
+ return spawnFunction(_compileScript).call({ |
+ 'mode': mode, |
+ 'outputDir': outputDir.toNativePath(), |
+ 'libPath': libPath.toNativePath() |
+ }).then((result) { |
+ if (result.first == 'success') return; |
+ throw new AsyncError(result[1], result[2]); |
+ }); |
+} |
+ |
+void _compileScript() { |
+ port.receive((message, replyTo) { |
+ new Future.of(() { |
+ var clientScript = (message['mode'] == MODE_STATIC) ? |
+ 'static' : 'live-nav'; |
+ var dartPath = pathos.join(message['libPath'], 'lib', '_internal', |
+ 'dartdoc', 'lib', 'src', 'client', 'client-$clientScript.dart'); |
+ var jsPath = pathos.join(message['outputDir'], 'client-$clientScript.js'); |
+ |
+ return dart2js.compile( |
+ new Path(dartPath), new Path(message['libPath']), |
+ options: const <String>['--categories=Client,Server']).then((jsCode) { |
+ writeString(new File(jsPath), jsCode); |
+ }); |
+ }).then((_) { |
+ replyTo.send(['success']); |
+ }).catchError((e) { |
+ replyTo.send(['error', e.error.toString(), e.stackTrace.toString()]); |
}); |
+ }); |
} |
/** |
@@ -261,11 +286,30 @@ class Dartdoc { |
/** Set this to select the libraries to exclude from the documentation. */ |
List<String> excludedLibraries = const <String>[]; |
+ /** The package root for `package:` imports. */ |
+ String _packageRoot; |
+ |
+ /** The map containing all the exports for each library. */ |
+ ExportMap _exports; |
+ |
/** |
* This list contains the libraries sorted in by the library name. |
*/ |
List<LibraryMirror> _sortedLibraries; |
+ /** A map from absolute paths of libraries to the libraries at those paths. */ |
+ Map<String, LibraryMirror> _librariesByPath; |
+ |
+ /** |
+ * A map from absolute paths of hidden libraries to lists of [Export]s that |
+ * export those libraries from visible libraries. This is used to determine |
+ * what public library any given entity belongs to. |
+ * |
+ * The lists of exports are sorted so that exports that hide the fewest number |
+ * of members come first. |
+ */ |
+ Map<String, List<Export>> _hiddenLibraryExports; |
+ |
/** The library that we're currently generating docs for. */ |
LibraryMirror _currentLibrary; |
@@ -403,16 +447,25 @@ class Dartdoc { |
return content; |
} |
- Future documentEntryPoint(Path entrypoint, Path libPath, Path packageRoot) { |
- return documentLibraries(<Path>[entrypoint], libPath, packageRoot); |
- } |
+ Future documentLibraries(List<Uri> libraryList, Path libPath, |
+ String packageRoot) { |
+ _packageRoot = packageRoot; |
+ _exports = new ExportMap.parse(libraryList, packageRoot); |
+ var librariesToAnalyze = _exports.allExportedFiles.toList(); |
+ librariesToAnalyze.addAll(libraryList.map((uri) { |
+ if (uri.scheme == 'file') return fileUriToPath(uri); |
+ // dart2js takes "dart:*" URIs as Path objects for some reason. |
+ return uri.toString(); |
+ })); |
+ |
+ var packageRootPath = packageRoot == null ? null : new Path(packageRoot); |
- Future documentLibraries(List<Path> libraryList, Path libPath, Path packageRoot) { |
// TODO(amouravski): make all of these print statements into logging |
// statements. |
print('Analyzing libraries...'); |
- return dart2js.analyze(libraryList, libPath, packageRoot: packageRoot, |
- options: COMPILER_OPTIONS) |
+ return dart2js.analyze( |
+ librariesToAnalyze.map((path) => new Path(path)).toList(), libPath, |
+ packageRoot: packageRootPath, options: COMPILER_OPTIONS) |
.then((MirrorSystem mirrors) { |
print('Generating documentation...'); |
_document(mirrors); |
@@ -430,6 +483,16 @@ class Dartdoc { |
displayName(y).toUpperCase()); |
}); |
+ _librariesByPath = <String, LibraryMirror>{}; |
+ for (var library in mirrors.libraries.values) { |
+ var path = _libraryPath(library); |
+ if (path == null) continue; |
+ path = pathos.normalize(pathos.absolute(path)); |
+ _librariesByPath[path] = library; |
+ } |
+ |
+ _hiddenLibraryExports = _generateHiddenLibraryExports(); |
+ |
// Generate the docs. |
if (mode == MODE_LIVE_NAV) { |
docNavigationJson(); |
@@ -446,6 +509,7 @@ class Dartdoc { |
generateAppCacheManifest(); |
} |
+ // TODO(nweiz): properly handle exports when generating JSON. |
// TODO(jacobr): handle arbitrary pub packages rather than just the system |
// libraries. |
var revision = '0'; |
@@ -484,6 +548,63 @@ class Dartdoc { |
_finished = true; |
} |
+ /** |
+ * Generate [_hiddenLibraryExports] from [_exports] and [_librariesByPath]. |
+ */ |
+ Map<String, List<Export>> _generateHiddenLibraryExports() { |
+ // First generate a map `exported path => exporter path => Export`. The |
+ // inner map makes it easier to merge multiple exports of the same library |
+ // by the same exporter. |
+ var hiddenLibraryExportMaps = <String, Map<String, Export>>{}; |
+ _exports.exports.forEach((exporter, exports) { |
+ var library = _librariesByPath[exporter]; |
+ // TODO(nweiz): remove this check when issue 9645 is fixed. |
+ if (library == null) return; |
+ if (!shouldIncludeLibrary(library)) return; |
+ for (var export in exports) { |
+ var library = _librariesByPath[export.path]; |
+ // TODO(nweiz): remove this check when issue 9645 is fixed. |
+ if (library == null) continue; |
+ if (shouldIncludeLibrary(library)) continue; |
+ |
+ var hiddenExports = _exports.transitiveExports(export.path) |
+ .map((transitiveExport) => export.compose(transitiveExport)) |
+ .toList(); |
+ hiddenExports.add(export); |
+ |
+ for (var hiddenExport in hiddenExports) { |
+ var exportsByExporterPath = hiddenLibraryExportMaps |
+ .putIfAbsent(hiddenExport.path, () => <String, Export>{}); |
+ addOrMergeExport(exportsByExporterPath, exporter, hiddenExport); |
+ } |
+ } |
+ }); |
+ |
+ // Now sort the values of the inner maps of `hiddenLibraryExportMaps` to get |
+ // the final value of `_hiddenLibraryExports`. |
+ var hiddenLibraryExports = <String, List<Export>>{}; |
+ hiddenLibraryExportMaps.forEach((exporteePath, exportsByExporterPath) { |
+ int rank(Export export) { |
+ if (export.show.isEmpty && export.hide.isEmpty) return 0; |
+ if (export.show.isEmpty) return export.hide.length; |
+ // Multiply by 1000 to ensure this sorts after an export with hides. |
+ return 1000 * export.show.length; |
+ } |
+ |
+ var exports = exportsByExporterPath.values.toList(); |
+ exports.sort((export1, export2) { |
+ var comparison = Comparable.compare(rank(export1), rank(export2)); |
+ if (comparison != 0) return comparison; |
+ |
+ var library1 = _librariesByPath[export1.exporter]; |
+ var library2 = _librariesByPath[export2.exporter]; |
+ return Comparable.compare(library1.displayName, library2.displayName); |
+ }); |
+ hiddenLibraryExports[exporteePath] = exports; |
+ }); |
+ return hiddenLibraryExports; |
+ } |
+ |
MdnComment lookupMdnComment(Mirror mirror) => null; |
void startFile(String path) { |
@@ -868,6 +989,9 @@ class Dartdoc { |
writeln('<div class="doc">${comment.html}</div>'); |
} |
+ // Document the visible libraries exported by this library. |
+ docExports(library); |
+ |
// Document the top-level members. |
docMembers(library); |
@@ -878,7 +1002,8 @@ class Dartdoc { |
final typedefs = <TypedefMirror>[]; |
final exceptions = <ClassMirror>[]; |
- for (ClassMirror type in orderByName(library.classes.values)) { |
+ var allClasses = _libraryClasses(library); |
+ for (ClassMirror type in orderByName(allClasses)) { |
if (!showPrivate && type.isPrivate) continue; |
if (isException(type)) { |
@@ -907,7 +1032,7 @@ class Dartdoc { |
writeFooter(); |
endFile(); |
- for (final type in library.classes.values) { |
+ for (final type in allClasses) { |
if (!showPrivate && type.isPrivate) continue; |
docType(type); |
@@ -955,8 +1080,9 @@ class Dartdoc { |
final typeTitle = |
'${typeName(type)} ${kind}'; |
- writeHeader('$typeTitle / ${displayName(type.library)} Library', |
- [displayName(type.library), libraryUrl(type.library), |
+ var library = _libraryFor(type); |
+ writeHeader('$typeTitle / ${displayName(library)} Library', |
+ [displayName(library), libraryUrl(library), |
typeName(type), typeUrl(type)]); |
writeln( |
''' |
@@ -1148,6 +1274,40 @@ class Dartdoc { |
return map; |
}(); |
+ void docExports(LibraryMirror library) { |
+ // TODO(nweiz): show `dart:` library exports. |
+ var exportLinks = _exports.transitiveExports(_libraryPath(library)) |
+ .map((export) { |
+ var library = _librariesByPath[export.path]; |
+ // TODO(nweiz): remove this check when issue 9645 is fixed. |
+ if (library == null) return null; |
+ // Only link to publically visible libraries. |
+ if (!shouldIncludeLibrary(library)) return null; |
+ |
+ var memberNames = export.show.isEmpty ? export.hide : export.show; |
+ var memberLinks = memberNames.map((name) { |
+ return md.renderToHtml([resolveNameReference( |
+ name, currentLibrary: library)]); |
+ }).join(', '); |
+ var combinator = ''; |
+ if (!export.show.isEmpty) { |
+ combinator = ' show $memberLinks'; |
+ } else if (!export.hide.isEmpty) { |
+ combinator = ' hide $memberLinks'; |
+ } |
+ |
+ return '<ul>${a(libraryUrl(library), displayName(library))}' |
+ '$combinator</ul>'; |
+ }).where((link) => link != null); |
+ |
+ if (!exportLinks.isEmpty) { |
+ writeln('<h3>Exports</h3>'); |
+ writeln('<ul>'); |
+ writeln(exportLinks.join('\n')); |
+ writeln('</ul>'); |
+ } |
+ } |
+ |
void docMembers(ContainerMirror host) { |
// Collect the different kinds of members. |
final staticMethods = []; |
@@ -1160,8 +1320,10 @@ class Dartdoc { |
final instanceSetters = new Map<String,MemberMirror>(); |
final constructors = []; |
- host.members.forEach((_, MemberMirror member) { |
- if (!showPrivate && member.isPrivate) return; |
+ var hostMembers = host is LibraryMirror ? |
+ _libraryMembers(host) : host.members.values; |
+ for (var member in hostMembers) { |
+ if (!showPrivate && member.isPrivate) continue; |
if (host is LibraryMirror || member.isStatic) { |
if (member is MethodMirror) { |
if (member.isGetter) { |
@@ -1175,7 +1337,7 @@ class Dartdoc { |
staticGetters[member.displayName] = member; |
} |
} |
- }); |
+ } |
if (host is ClassMirror) { |
var iterable = new HierarchyIterable(host, includeType: true); |
@@ -1219,28 +1381,28 @@ class Dartdoc { |
if (member is MethodMirror) { |
if (member.isGetter) { |
instanceGetters[member.displayName] = member; |
- if (member.owner == host) { |
+ if (_ownerFor(member) == host) { |
allPropertiesInherited = false; |
} |
} else if (member.isSetter) { |
instanceSetters[member.displayName] = member; |
- if (member.owner == host) { |
+ if (_ownerFor(member) == host) { |
allPropertiesInherited = false; |
} |
} else if (member.isOperator) { |
instanceOperators.add(member); |
- if (member.owner == host) { |
+ if (_ownerFor(member) == host) { |
allOperatorsInherited = false; |
} |
} else { |
instanceMethods.add(member); |
- if (member.owner == host) { |
+ if (_ownerFor(member) == host) { |
allMethodsInherited = false; |
} |
} |
} else if (member is VariableMirror) { |
instanceGetters[member.displayName] = member; |
- if (member.owner == host) { |
+ if (_ownerFor(member) == host) { |
allPropertiesInherited = false; |
} |
} |
@@ -1305,7 +1467,7 @@ class Dartdoc { |
} else { |
DocComment getterComment = getMemberComment(getter); |
DocComment setterComment = getMemberComment(setter); |
- if (getter.owner != setter.owner || |
+ if (_ownerFor(getter) != _ownerFor(setter) || |
getterComment != null && setterComment != null) { |
// Both have comments or are not declared in the same class |
// => Documents separately. |
@@ -1376,7 +1538,7 @@ class Dartdoc { |
} |
bool showCode = includeSource && !isAbstract; |
- bool inherited = host != member.owner; |
+ bool inherited = host != member.owner && member.owner is! LibraryMirror; |
writeln('<div class="method${inherited ? ' inherited': ''}">' |
'<h4 id="${memberAnchor(member)}">'); |
@@ -1467,7 +1629,7 @@ class Dartdoc { |
_totalMembers++; |
_currentMember = getter; |
- bool inherited = host != getter.owner; |
+ bool inherited = host != getter.owner && getter.owner is! LibraryMirror; |
writeln('<div class="field${inherited ? ' inherited' : ''}">' |
'<h4 id="${memberAnchor(getter)}">'); |
@@ -1674,13 +1836,13 @@ class Dartdoc { |
// Always get the generic type to strip off any type parameters or |
// arguments. If the type isn't generic, genericType returns `this`, so it |
// works for non-generic types too. |
- return '${sanitize(displayName(type.library))}/' |
+ return '${sanitize(displayName(_libraryFor(type)))}/' |
'${type.originalDeclaration.simpleName}.html'; |
} |
/** Gets the URL for the documentation for [member]. */ |
String memberUrl(MemberMirror member) { |
- String url = typeUrl(member.owner); |
+ String url = typeUrl(_ownerFor(member)); |
return '$url#${memberAnchor(member)}'; |
} |
@@ -1759,9 +1921,10 @@ class Dartdoc { |
assert(type is ClassMirror); |
// Link to the type. |
- if (shouldLinkToPublicApi(type.library)) { |
+ var library = _libraryFor(type); |
+ if (shouldLinkToPublicApi(library)) { |
write('<a href="$API_LOCATION${typeUrl(type)}">${type.simpleName}</a>'); |
- } else if (shouldIncludeLibrary(type.library)) { |
+ } else if (shouldIncludeLibrary(library)) { |
write(a(typeUrl(type), type.simpleName)); |
} else { |
write(type.simpleName); |
@@ -1979,6 +2142,86 @@ class Dartdoc { |
return type.simpleName.endsWith('Exception') || |
type.simpleName.endsWith('Error'); |
} |
+ |
+ /** |
+ * Returns the absolute path to [library] on the filesystem, or `null` if the |
+ * library doesn't exist on the local filesystem. |
+ */ |
+ String _libraryPath(LibraryMirror library) => |
+ importUriToPath(library.uri, packageRoot: _packageRoot); |
+ |
+ /** |
+ * Returns a list of classes in [library], including classes it exports from |
+ * hidden libraries. |
+ */ |
+ List<ClassMirror> _libraryClasses(LibraryMirror library) => |
+ _libraryContents(library, (lib) => lib.classes.values); |
+ |
+ /** |
+ * Returns a list of top-level members in [library], including members it |
+ * exports from hidden libraries. |
+ */ |
+ List<MemberMirror> _libraryMembers(LibraryMirror library) => |
+ _libraryContents(library, (lib) => lib.members.values); |
+ |
+ |
+ /** |
+ * Returns a list of elements in [library], including elements it exports from |
+ * hidden libraries. [fn] should return the element list for a single library, |
+ * which will then be merged across all exported libraries. |
+ */ |
+ List<DeclarationMirror> _libraryContents(LibraryMirror library, |
+ List<DeclarationMirror> fn(LibraryMirror)) { |
+ var contents = fn(library).toList(); |
+ var path = _libraryPath(library); |
+ if (path == null || _exports.exports[path] == null) return contents; |
+ |
+ |
+ contents.addAll(_exports.exports[path].expand((export) { |
+ var exportedLibrary = _librariesByPath[export.path]; |
+ // TODO(nweiz): remove this check when issue 9645 is fixed. |
+ if (exportedLibrary == null) return []; |
+ if (shouldIncludeLibrary(exportedLibrary)) return []; |
+ return fn(exportedLibrary).where((declaration) => |
+ export.isMemberVisible(declaration.displayName)); |
+ })); |
+ return contents; |
+ } |
+ |
+ /** |
+ * Returns the library in which [type] was defined. If [type] was defined in a |
+ * hidden library that was exported by another library, this returns the |
+ * exporter. |
+ */ |
+ LibraryMirror _libraryFor(TypeMirror type) => |
+ _visibleLibrary(type.library, type.displayName); |
+ |
+ /** |
+ * Returns the owner of [declaration]. If [declaration]'s owner is a hidden |
+ * library that was exported by another library, this returns the exporter. |
+ */ |
+ DeclarationMirror _ownerFor(DeclarationMirror declaration) { |
+ var owner = declaration.owner; |
+ if (owner is! LibraryMirror) return owner; |
+ return _visibleLibrary(owner, declaration.displayName); |
+ } |
+ |
+ /** |
+ * Returns the best visible library that exports [name] from [library]. If |
+ * [library] is public, it will be returned. |
+ */ |
+ LibraryMirror _visibleLibrary(LibraryMirror library, String name) { |
+ if (library == null) return null; |
+ |
+ var exports = _hiddenLibraryExports[_libraryPath(library)]; |
+ if (exports == null) return library; |
+ |
+ var export = exports.firstWhere( |
+ (exp) => exp.isMemberVisible(name), |
+ orElse: () => null); |
+ if (export == null) return library; |
+ return _librariesByPath[export.exporter]; |
+ } |
} |
/** |