Revert "Revert "Attempt to re-commit Dartdoc exports.""

This reverts commit 1340ca938883c2810fbc4e6d4ed4c52b68694faf.

Review URL: https://codereview.chromium.org//14194003

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@21320 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
amouravski@google.com 2013-04-11 22:42:55 +00:00
parent f9bc3af4b9
commit f9e2907a23
10 changed files with 1214 additions and 120 deletions

View file

@ -23,4 +23,4 @@ if test -f "$BIN_DIR/../lib/_internal/dartdoc/bin/dartdoc.dart.snapshot"; then
echo Using snapshot "$BIN_DIR/../lib/_internal/dartdoc/bin/dartdoc.dart.snapshot" 1>&2
SNAPSHOT="--use-script-snapshot=$BIN_DIR/../lib/_internal/dartdoc/bin/dartdoc.dart.snapshot"
fi
exec "$BIN_DIR"/dart --heap_growth_rate=32 "--package-root=$BIN_DIR/../packages/" $SNAPSHOT "$BIN_DIR/../lib/_internal/dartdoc/bin/dartdoc.dart" $COLORS "$@"
exec "$BIN_DIR"/dart --heap_growth_rate=32 "--package-root=$BIN_DIR/../packages/" $SNAPSHOT "$BIN_DIR/../lib/_internal/dartdoc/bin/dartdoc.dart" "--package-root=$BIN_DIR/../packages/" $COLORS "$@"

View file

@ -18,9 +18,11 @@ library dartdoc;
import 'dart:async';
import 'dart:io';
import 'dart:uri';
// TODO(rnystrom): Use "package:" URL (#4968).
import '../lib/dartdoc.dart';
import '../lib/src/dartdoc/utils.dart';
import 'package:args/args.dart';
import 'package:pathos/path.dart' as path;
@ -39,7 +41,7 @@ main() {
final Path libPath = scriptDir.append('../../../../');
Path packageRoot;
String packageRoot;
argParser.addFlag('no-code',
help: 'Do not include source code in the documentation.',
@ -166,7 +168,7 @@ main() {
' the entrypoint.',
callback: (packageDir) {
if(packageDir != null) {
packageRoot = new Path(packageDir);
packageRoot = packageDir;
}
});
@ -175,7 +177,7 @@ main() {
help: 'Deprecated: same as --package-root.',
callback: (packageDir) {
if(packageDir != null) {
packageRoot = new Path(packageDir);
packageRoot = packageDir;
}
});
@ -188,21 +190,22 @@ main() {
exit(1);
}
final entrypoints = <Path>[];
final entrypoints = <Uri>[];
try {
final option = argParser.parse(args);
// This checks to see if the root of all entrypoints is the same.
// If it is not, then we display a warning, as package imports might fail.
var entrypointRoot;
for(final arg in option.rest) {
var entrypoint = new Path(arg);
entrypoints.add(entrypoint);
for (final entrypoint in option.rest) {
var uri = Uri.parse(entrypoint);
if (uri.scheme == '') uri = pathToFileUri(entrypoint);
entrypoints.add(uri);
if (uri.scheme != 'file') continue;
if (entrypointRoot == null) {
entrypointRoot = entrypoint.directoryPath;
} else if (entrypointRoot.toNativePath() !=
entrypoint.directoryPath.toNativePath()) {
entrypointRoot = path.dirname(entrypoint);
} else if (entrypointRoot != path.dirname(entrypoint)) {
print('Warning: entrypoints are at different directories. "package:"'
' imports may fail.');
}
@ -220,40 +223,18 @@ main() {
exit(1);
}
if (packageRoot == null) {
// Check if there's a `packages` directory in the entry point directory.
var script = path.normalize(path.absolute(entrypoints[0].toNativePath()));
var dir = path.join(path.dirname(script), 'packages/');
if (new Directory(dir).existsSync()) {
// TODO(amouravski): convert all of dartdoc to use pathos.
packageRoot = new Path(dir);
} else {
// If there is not, then check if the entrypoint is somewhere in a `lib`
// directory.
dir = path.dirname(script);
var parts = path.split(dir);
var libDir = parts.lastIndexOf('lib');
if (libDir > 0) {
packageRoot = new Path(path.join(path.joinAll(parts.take(libDir)),
'packages'));
}
}
}
if (packageRoot == null) packageRoot = _getPackageRoot(entrypoints);
cleanOutputDirectory(dartdoc.outputDir);
// Start the analysis and documentation.
dartdoc.documentLibraries(entrypoints, libPath, packageRoot)
.then((_) {
print('Copying static files...');
Future.wait([
// Prepare the dart2js script code and copy static resources.
// TODO(amouravski): move compileScript out and pre-generate the client
// scripts. This takes a long time and the js hardly ever changes.
compileScript(dartdoc.mode, dartdoc.outputDir, libPath),
copyDirectory(scriptDir.append('../static'), dartdoc.outputDir)
]);
})
// Prepare the dart2js script code and copy static resources.
// TODO(amouravski): move compileScript out and pre-generate the client
// scripts. This takes a long time and the js hardly ever changes.
.then((_) => compileScript(dartdoc.mode, dartdoc.outputDir, libPath))
.then((_) => copyDirectory(scriptDir.append('../static'),
dartdoc.outputDir))
.then((_) {
print(dartdoc.status);
if (dartdoc.totals == 0) {
@ -267,3 +248,25 @@ main() {
})
.whenComplete(() => dartdoc.cleanup());
}
String _getPackageRoot(List<Uri> entrypoints) {
// Check if there's a `packages` directory in the entry point directory.
var fileEntrypoint = entrypoints.firstWhere(
(entrypoint) => entrypoint.scheme == 'file',
orElse: () => null);
if (fileEntrypoint == null) return;
var script = path.normalize(path.absolute(fileUriToPath(fileEntrypoint)));
var dir = path.join(path.dirname(script), 'packages/');
if (new Directory(dir).existsSync()) return dir;
// If there is not, then check if the entrypoint is somewhere in a `lib`
// directory.
var parts = path.split(path.dirname(script));
var libDir = parts.lastIndexOf('lib');
if (libDir > 0) {
return path.join(path.joinAll(parts.take(libDir)), 'packages');
} else {
return null;
}
}

View file

@ -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
@ -107,6 +110,7 @@ String displayName(LibraryMirror library) {
* event loop has had a chance to pump (i.e. after `main()` has returned).
*/
Future copyDirectory(Path from, Path to) {
print('Copying static files...');
final completer = new Completer();
final fromDir = new Directory.fromPath(from);
fromDir.list(recursive: false).listen(
@ -131,16 +135,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 +287,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 +448,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 +484,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 +510,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 +549,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 +990,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 +1003,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 +1033,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 +1081,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 +1275,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 +1321,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 +1338,7 @@ class Dartdoc {
staticGetters[member.displayName] = member;
}
}
});
}
if (host is ClassMirror) {
var iterable = new HierarchyIterable(host, includeType: true);
@ -1219,28 +1382,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 +1468,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 +1539,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 +1630,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 +1837,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 +1922,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 +2143,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];
}
}
/**

View file

@ -2,9 +2,18 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of dartdoc;
// Generic utility functions.
library utils;
import 'dart:io';
import 'dart:uri';
import 'dart:math' as math;
import 'package:pathos/path.dart' as pathos;
import '../../../../compiler/implementation/mirrors/mirrors.dart';
import '../export_map.dart';
/** Turns [name] into something that's safe to use as a file name. */
String sanitize(String name) => name.replaceAll(':', '_').replaceAll('/', '_');
@ -40,7 +49,7 @@ String repeat(String text, int count, {String separator}) {
/** Removes up to [indentation] leading whitespace characters from [text]. */
String unindent(String text, int indentation) {
var start;
for (start = 0; start < min(indentation, text.length); start++) {
for (start = 0; start < math.min(indentation, text.length); start++) {
// Stop if we hit a non-whitespace character.
if (text[start] != ' ') break;
}
@ -77,3 +86,76 @@ void writeString(File file, String text) {
randomAccessFile.writeStringSync(text);
randomAccessFile.closeSync();
}
/**
* Converts [uri], which should come from a Dart import or export, to a local
* filesystem path. [basePath] is the base directory to use when converting
* relative URIs; without it, relative URIs will not be converted. [packageRoot]
* is the `packages` directory to use when converting `package:` URIs; without
* it, `package:` URIs will not be converted.
*
* If a URI cannot be converted, this will return `null`.
*/
String importUriToPath(Uri uri, {String basePath, String packageRoot}) {
if (uri.scheme == 'file') return fileUriToPath(uri);
if (basePath != null && uri.scheme == '') {
return pathos.normalize(pathos.absolute(pathos.join(basePath, uri.path)));
}
if (packageRoot != null && uri.scheme == 'package') {
return pathos.normalize(pathos.absolute(
pathos.join(packageRoot, uri.path)));
}
// Ignore unsupported schemes.
return null;
}
/** Converts a `file:` [Uri] to a local path string. */
String fileUriToPath(Uri uri) {
if (uri.scheme != 'file') {
throw new ArgumentError("Uri $uri must have scheme 'file:'.");
}
if (Platform.operatingSystem != 'windows') return pathos.normalize(uri.path);
return pathos.normalize(uri.path.replaceFirst("/", "").replaceAll("/", "\\"));
}
/** Converts a local path string to a `file:` [Uri]. */
Uri pathToFileUri(String path) {
path = pathos.absolute(path);
if (Platform.operatingSystem != 'windows') {
return Uri.parse('file://$path');
} else {
return Uri.parse('file:///${path.replaceAll("\\", "/")}');
}
}
/**
* If [map] contains an [Export] under [key], this merges that with [export].
* Otherwise, it sets [key] to [export].
*/
void addOrMergeExport(Map<String, Export> map, String key, Export export) {
if (map.containsKey(key)) {
map[key] = map[key].merge(export);
} else {
map[key] = export;
}
}
/// A pair of values.
class Pair<E, F> {
E first;
F last;
Pair(this.first, this.last);
String toString() => '($first, $last)';
bool operator==(other) {
if (other is! Pair) return false;
return other.first == first && other.last == last;
}
int get hashCode => first.hashCode ^ last.hashCode;
}

View file

@ -0,0 +1,342 @@
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// This library uses the Dart analyzer to find the exports for a set of
/// libraries. It stores these exports in an [ExportMap]. This is used to
/// display exported members as part of the exporting library, since dart2js
/// doesn't provide this information itself.
library export_map;
import 'dart:io';
import 'dart:uri';
import 'package:analyzer_experimental/src/generated/ast.dart';
import 'package:analyzer_experimental/src/generated/error.dart';
import 'package:analyzer_experimental/src/generated/parser.dart';
import 'package:analyzer_experimental/src/generated/scanner.dart';
import 'package:analyzer_experimental/src/generated/source.dart';
import 'package:pathos/path.dart' as pathos;
import 'dartdoc/utils.dart';
/// A class that tracks which libraries export which other libraries.
class ExportMap {
/// A map from libraries to their [Export]s.
///
/// Each key is the absolute path of a library on the filesystem, and each
/// value is a list of [Export]s for that library. There's guaranteed to be
/// only one [Export] of a given library in a given list.
final Map<String, List<Export>> exports;
/// A cache of the transitive exports for each library. The keys are paths to
/// libraries. The values are maps from the exported path to the [Export]
/// objects, to make it easier to merge multiple exports of the same library.
final _transitiveExportsByPath = <String, Map<String, Export>>{};
/// Parse an export map from a set of [libraries], which should be Dart import
/// [Uri]s. [packageRoot] should be the path to the `packages` directory to
/// use when resolving `package:` imports and libraries. Libraries that are
/// not available on the local machine will be ignored.
///
/// In addition to parsing the exports in [libraries], this will parse the
/// exports in all libraries transitively reachable from [libraries] via
/// `import` or `export`.
factory ExportMap.parse(Iterable<Uri> libraries, String packageRoot) {
var exports = <String, List<Export>>{};
void traverse(String path) {
if (exports.containsKey(path)) return;
var importsAndExports;
try {
importsAndExports = _importsAndExportsForFile(path, packageRoot);
} on FileIOException catch (_) {
// Ignore unreadable/nonexistent files.
return;
}
var exportsForLibrary = <String, Export>{};
for (var export in importsAndExports.last) {
addOrMergeExport(exportsForLibrary, export.path, export);
}
exports[path] = new List.from(exportsForLibrary.values);
exports[path].map((directive) => directive.path).forEach(traverse);
importsAndExports.first.forEach(traverse);
}
for (var library in libraries) {
var path = importUriToPath(library, packageRoot: packageRoot);
if (path != null) traverse(path);
}
return new ExportMap._(exports);
}
ExportMap._(this.exports);
/// Returns a list of all the paths of exported libraries that [this] is aware
/// of.
List<String> get allExportedFiles => exports.values.expand((e) => e)
.map((directive) => directive.path).toList();
/// Returns a list of all exports that [library] transitively exports. This
/// means that if [library] exports another library that in turn exports a
/// third, the third library will be included in the returned list.
///
/// This will automatically handle nested `hide` and `show` directives on the
/// exports, as well as merging multiple exports of the same library.
List<Export> transitiveExports(String library) {
Map<String, Export> _getTransitiveExportsByPath(String path) {
if (_transitiveExportsByPath.containsKey(path)) {
return _transitiveExportsByPath[path];
}
var exportsByPath = <String, Export>{};
_transitiveExportsByPath[path] = exportsByPath;
if (exports[path] == null) return exportsByPath;
for (var export in exports[path]) {
exportsByPath[export.path] = export;
}
for (var export in exports[path]) {
for (var subExport in _getTransitiveExportsByPath(export.path).values) {
subExport = export.compose(subExport);
if (exportsByPath.containsKey(subExport.path)) {
subExport = subExport.merge(exportsByPath[subExport.path]);
}
exportsByPath[subExport.path] = subExport;
}
}
return exportsByPath;
}
var path = pathos.normalize(pathos.absolute(library));
return _getTransitiveExportsByPath(path).values.toList();
}
}
/// A class that represents one library exporting another.
class Export {
/// The absolute path of the library that contains this export.
final String exporter;
/// The absolute path of the library being exported.
final String path;
/// The set of identifiers that are explicitly being exported. If this is
/// non-empty, no identifiers other than these will be visible.
///
/// One or both of [show] and [hide] will always be empty.
Set<String> get show => _show;
Set<String> _show;
/// The set of identifiers that are not exported.
///
/// One or both of [show] and [hide] will always be empty.
Set<String> get hide => _hide;
Set<String> _hide;
/// Whether or not members exported are hidden by default.
bool get _hideByDefault => !show.isEmpty;
/// Creates a new export.
///
/// This will normalize [show] and [hide] so that if both are non-empty, only
/// [show] will be set.
Export(this.exporter, this.path, {Iterable<String> show,
Iterable<String> hide}) {
_show = new Set<String>.from(show == null ? [] : show);
_hide = new Set<String>.from(hide == null ? [] : hide);
if (!_show.isEmpty) {
_show.removeAll(_hide);
_hide = new Set<String>();
}
}
/// Returns a new [Export] that represents [this] composed with [nested], as
/// though [this] was used to export a library that in turn exported [nested].
Export compose(Export nested) {
var show = new Set<String>();
var hide = new Set<String>();
if (this._hideByDefault) {
show.addAll(this.show);
if (nested._hideByDefault) {
show.retainAll(nested.show);
} else {
show.removeAll(nested.hide);
}
} else if (nested._hideByDefault) {
show.addAll(nested.show);
show.removeAll(this.hide);
} else {
hide.addAll(this.hide);
hide.addAll(nested.hide);
}
return new Export(this.exporter, nested.path, show: show, hide: hide);
}
/// Returns a new [Export] that merges [this] with [nested], as though both
/// exports were included in the same library.
///
/// [this] and [other] must have the same values for [exporter] and [path].
Export merge(Export other) {
if (this.path != other.path) {
throw new ArgumentError("Can't merge two Exports with different paths: "
"export '$path' from '$exporter' and export '${other.path}' from "
"'${other.exporter}'.");
} if (this.exporter != other.exporter) {
throw new ArgumentError("Can't merge two Exports with different "
"exporters: export '$path' from '$exporter' and export "
"'${other.path}' from '${other.exporter}'.");
}
var show = new Set<String>();
var hide = new Set<String>();
if (this._hideByDefault) {
if (other._hideByDefault) {
show.addAll(this.show);
show.addAll(other.show);
} else {
hide.addAll(other.hide);
hide.removeAll(this.show);
}
} else {
hide.addAll(this.hide);
if (other._hideByDefault) {
hide.removeAll(other.show);
} else {
hide.retainAll(other.hide);
}
}
return new Export(exporter, path, show: show, hide: hide);
}
/// Returns whether or not a member named [name] is visible through this
/// import, as goverend by [show] and [hide].
bool isMemberVisible(String name) =>
_hideByDefault ? show.contains(name) : !hide.contains(name);
bool operator==(other) => other is Export && other.exporter == exporter &&
other.path == path && show.containsAll(other.show) &&
other.show.containsAll(show) && hide.containsAll(other.hide) &&
other.hide.containsAll(hide);
int get hashCode {
var hashCode = exporter.hashCode ^ path.hashCode;
hashCode = show.reduce(hashCode, (hash, name) => hash ^ name.hashCode);
return hide.reduce(hashCode, (hash, name) => hash ^ name.hashCode);
}
String toString() {
var combinator = '';
if (!show.isEmpty) {
combinator = ' show ${show.join(', ')}';
} else if (!hide.isEmpty) {
combinator = ' hide ${hide.join(', ')}';
}
return "export '$path'$combinator (from $exporter)";
}
}
/// Returns a list of imports and a list of exports for the dart library at
/// [file]. [packageRoot] is used to resolve `package:` URLs.
///
/// The imports are a list of absolute paths, while the exports are [Export]
/// objects.
Pair<List<String>, List<Export>> _importsAndExportsForFile(String file,
String packageRoot) {
var collector = new _ImportExportCollector();
_parseFile(file).accept(collector);
var imports = collector.imports.map((import) {
return _pathForDirective(import, pathos.dirname(file), packageRoot);
}).where((import) => import != null).toList();
var exports = collector.exports.map((export) {
var path = _pathForDirective(export, pathos.dirname(file), packageRoot);
if (path == null) return null;
path = pathos.normalize(pathos.absolute(path));
var show = export.combinators
.where((combinator) => combinator is ShowCombinator)
.expand((combinator) => combinator.shownNames.map((name) => name.name));
var hide = export.combinators
.where((combinator) => combinator is HideCombinator)
.expand((combinator) =>
combinator.hiddenNames.map((name) => name.name));
return new Export(file, path, show: show, hide: hide);
}).where((export) => export != null).toList();
return new Pair<List<String>, List<Export>>(imports, exports);
}
/// Returns the absolute path to the library imported by [directive], or `null`
/// if it doesn't refer to a file on the local filesystem.
///
/// [basePath] is the path from which relative imports should be resolved.
/// [packageRoot] is the path from which `package:` imports should be resolved.
String _pathForDirective(NamespaceDirective directive, String basePath,
String packageRoot) {
var uri = Uri.parse(_stringLiteralToString(directive.uri));
var path = importUriToPath(uri, basePath: basePath, packageRoot: packageRoot);
if (path == null) return null;
return pathos.normalize(pathos.absolute(path));
}
/// Parses a Dart file into an AST.
CompilationUnit _parseFile(String path) {
var contents = new File(path).readAsStringSync();
var errorCollector = new _ErrorCollector();
var scanner = new StringScanner(null, contents, errorCollector);
var token = scanner.tokenize();
var parser = new Parser(null, errorCollector);
var unit = parser.parseCompilationUnit(token);
unit.lineInfo = new LineInfo(scanner.lineStarts);
if (!errorCollector.errors.isEmpty) {
throw new FormatException(
errorCollector.errors.map((e) => e.toString()).join("\n"));
}
return unit;
}
/// A simple error listener that collects errors into a list.
class _ErrorCollector extends AnalysisErrorListener {
final errors = <AnalysisError>[];
_ErrorCollector();
void onError(AnalysisError error) => errors.add(error);
}
/// A simple visitor that collects import and export nodes.
class _ImportExportCollector extends GeneralizingASTVisitor {
final imports = <ImportDirective>[];
final exports = <ExportDirective>[];
_ImportExportCollector();
visitImportDirective(ImportDirective node) => imports.add(node);
visitExportDirective(ExportDirective node) => exports.add(node);
}
// TODO(nweiz): fold this into the analyzer (issue 9781).
/// Converts an AST node representing a string literal into a [String].
String _stringLiteralToString(StringLiteral literal) {
if (literal is AdjacentStrings) {
return literal.strings.map(_stringLiteralToString).join();
} else if (literal is SimpleStringLiteral) {
return literal.value;
} else {
throw new ArgumentError('Unknown string type for $literal');
}
}

View file

@ -3,6 +3,7 @@ description: >
Libraries for generating documentation from Dart source code.
dependencies:
analyzer_experimental: ">=0.4.5 <1.0.0"
args: ">=0.4.2 <1.0.0"
pathos: ">=0.4.2 <1.0.0"

View file

@ -17,42 +17,6 @@ import '../lib/markdown.dart';
import 'markdown_test.dart';
main() {
group('countOccurrences', () {
test('empty text returns 0', () {
expect(dd.countOccurrences('', 'needle'), equals(0));
});
test('one occurrence', () {
expect(dd.countOccurrences('bananarama', 'nara'), equals(1));
});
test('multiple occurrences', () {
expect(dd.countOccurrences('bananarama', 'a'), equals(5));
});
test('overlapping matches do not count', () {
expect(dd.countOccurrences('bananarama', 'ana'), equals(1));
});
});
group('repeat', () {
test('zero times returns an empty string', () {
expect(dd.repeat('ba', 0), isEmpty);
});
test('one time returns the string', () {
expect(dd.repeat('ba', 1), equals('ba'));
});
test('multiple times', () {
expect(dd.repeat('ba', 3), equals('bababa'));
});
test('multiple times with a separator', () {
expect(dd.repeat('ba', 3, separator: ' '), equals('ba ba ba'));
});
});
group('isAbsolute', () {
final doc = new dd.Dartdoc();

View file

@ -0,0 +1,400 @@
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';
import 'dart:uri';
import 'package:pathos/path.dart' as pathos;
import 'package:unittest/unittest.dart';
import '../lib/src/export_map.dart';
import '../lib/src/dartdoc/utils.dart';
String tempDir;
main() {
group('ExportMap', () {
setUp(createTempDir);
tearDown(deleteTempDir);
test('with an empty library', () {
createLibrary('lib.dart');
var map = parse(['lib.dart']);
var expectedExports = {};
expectedExports[libPath('lib.dart')] = [];
expect(map.exports, equals(expectedExports));
expect(map.allExportedFiles, isEmpty);
expect(map.transitiveExports(libPath('lib.dart')), isEmpty);
expect(map.transitiveExports(libPath('nonexistent.dart')), isEmpty);
});
test('with one library with one export', () {
createLibrary('a.dart', 'export "b.dart";');
createLibrary('b.dart');
var map = parse(['a.dart']);
expect(map.exports[libPath('a.dart')], unorderedEquals([
new Export(libPath('a.dart'), libPath('b.dart'))
]));
expect(map.transitiveExports(libPath('a.dart')), unorderedEquals([
new Export(libPath('a.dart'), libPath('b.dart'))
]));
expect(map.allExportedFiles, unorderedEquals([libPath('b.dart')]));
expect(map.exports[libPath('b.dart')], isEmpty);
expect(map.transitiveExports(libPath('b.dart')), isEmpty);
});
test('with one library with multiple exports', () {
createLibrary('a.dart', 'export "b.dart";\nexport "c.dart";');
createLibrary('b.dart');
createLibrary('c.dart');
var map = parse(['a.dart']);
expect(map.exports[libPath('a.dart')], unorderedEquals([
new Export(libPath('a.dart'), libPath('b.dart')),
new Export(libPath('a.dart'), libPath('c.dart'))
]));
expect(map.transitiveExports(libPath('a.dart')), unorderedEquals([
new Export(libPath('a.dart'), libPath('b.dart')),
new Export(libPath('a.dart'), libPath('c.dart'))
]));
expect(map.allExportedFiles, unorderedEquals([
libPath('b.dart'), libPath('c.dart')
]));
expect(map.exports[libPath('b.dart')], isEmpty);
expect(map.transitiveExports(libPath('b.dart')), isEmpty);
expect(map.exports[libPath('c.dart')], isEmpty);
expect(map.transitiveExports(libPath('c.dart')), isEmpty);
});
test('with two libraries each with one export', () {
createLibrary('a.dart', 'export "a_export.dart";');
createLibrary('b.dart', 'export "b_export.dart";');
createLibrary('a_export.dart');
createLibrary('b_export.dart');
var map = parse(['a.dart', 'b.dart']);
expect(map.exports[libPath('a.dart')], unorderedEquals([
new Export(libPath('a.dart'), libPath('a_export.dart')),
]));
expect(map.transitiveExports(libPath('a.dart')), unorderedEquals([
new Export(libPath('a.dart'), libPath('a_export.dart')),
]));
expect(map.transitiveExports(libPath('b.dart')), unorderedEquals([
new Export(libPath('b.dart'), libPath('b_export.dart')),
]));
expect(map.exports[libPath('b.dart')], unorderedEquals([
new Export(libPath('b.dart'), libPath('b_export.dart'))
]));
expect(map.allExportedFiles, unorderedEquals([
libPath('a_export.dart'), libPath('b_export.dart')
]));
expect(map.exports[libPath('a_export.dart')], isEmpty);
expect(map.transitiveExports(libPath('a_export.dart')), isEmpty);
expect(map.exports[libPath('b_export.dart')], isEmpty);
expect(map.transitiveExports(libPath('b_export.dart')), isEmpty);
});
test('with a transitive export', () {
createLibrary('a.dart', 'export "b.dart";');
createLibrary('b.dart', 'export "c.dart";');
createLibrary('c.dart');
var map = parse(['a.dart']);
expect(map.exports[libPath('a.dart')], unorderedEquals([
new Export(libPath('a.dart'), libPath('b.dart')),
]));
expect(map.transitiveExports(libPath('a.dart')), unorderedEquals([
new Export(libPath('a.dart'), libPath('b.dart')),
new Export(libPath('a.dart'), libPath('c.dart')),
]));
expect(map.exports[libPath('b.dart')], unorderedEquals([
new Export(libPath('b.dart'), libPath('c.dart')),
]));
expect(map.transitiveExports(libPath('b.dart')), unorderedEquals([
new Export(libPath('b.dart'), libPath('c.dart')),
]));
expect(map.allExportedFiles, unorderedEquals([
libPath('b.dart'), libPath('c.dart')
]));
expect(map.exports[libPath('c.dart')], isEmpty);
expect(map.transitiveExports(libPath('c.dart')), isEmpty);
});
test('with an export through an import', () {
createLibrary('a.dart', 'import "b.dart";');
createLibrary('b.dart', 'export "c.dart";');
createLibrary('c.dart');
var map = parse(['a.dart']);
expect(map.exports[libPath('b.dart')], unorderedEquals([
new Export(libPath('b.dart'), libPath('c.dart')),
]));
expect(map.transitiveExports(libPath('b.dart')), unorderedEquals([
new Export(libPath('b.dart'), libPath('c.dart')),
]));
expect(map.allExportedFiles, unorderedEquals([libPath('c.dart')]));
expect(map.exports[libPath('a.dart')], isEmpty);
expect(map.exports[libPath('c.dart')], isEmpty);
expect(map.transitiveExports(libPath('a.dart')), isEmpty);
expect(map.transitiveExports(libPath('c.dart')), isEmpty);
});
test('with an export with a show combinator', () {
createLibrary('a.dart', 'export "b.dart" show x, y;');
createLibrary('b.dart');
var map = parse(['a.dart']);
expect(map.exports[libPath('a.dart')], unorderedEquals([
new Export(libPath('a.dart'), libPath('b.dart'), show: ['x', 'y'])
]));
});
test('with an export with a hide combinator', () {
createLibrary('a.dart', 'export "b.dart" hide x, y;');
createLibrary('b.dart');
var map = parse(['a.dart']);
expect(map.exports[libPath('a.dart')], unorderedEquals([
new Export(libPath('a.dart'), libPath('b.dart'), hide: ['x', 'y'])
]));
});
test('with an export with a show and a hide combinator', () {
createLibrary('a.dart', 'export "b.dart" show x, y hide y, z;');
createLibrary('b.dart');
var map = parse(['a.dart']);
expect(map.exports[libPath('a.dart')], unorderedEquals([
new Export(libPath('a.dart'), libPath('b.dart'), show: ['x'])
]));
});
test('composes transitive exports', () {
createLibrary('a.dart', 'export "b.dart" hide x;');
createLibrary('b.dart', 'export "c.dart" hide y;');
createLibrary('c.dart');
var map = parse(['a.dart']);
expect(map.transitiveExports(libPath('a.dart')), unorderedEquals([
new Export(libPath('a.dart'), libPath('b.dart'), hide: ['x']),
new Export(libPath('a.dart'), libPath('c.dart'), hide: ['x', 'y'])
]));
});
test('merges adjacent exports', () {
createLibrary('a.dart', '''
export "b.dart" show x, y;
export "b.dart" hide y, z;
''');
createLibrary('b.dart');
var map = parse(['a.dart']);
expect(map.exports[libPath('a.dart')], unorderedEquals([
new Export(libPath('a.dart'), libPath('b.dart'), hide: ['z']),
]));
expect(map.transitiveExports(libPath('a.dart')), unorderedEquals([
new Export(libPath('a.dart'), libPath('b.dart'), hide: ['z']),
]));
});
test('merges adjacent exports transitively', () {
createLibrary('a.dart', 'export "b.dart";\nexport "c.dart";');
createLibrary('b.dart', 'export "d.dart" show x, y;');
createLibrary('c.dart', 'export "d.dart" hide y, z;');
createLibrary('d.dart');
var map = parse(['a.dart']);
expect(map.exports[libPath('a.dart')], unorderedEquals([
new Export(libPath('a.dart'), libPath('b.dart')),
new Export(libPath('a.dart'), libPath('c.dart')),
]));
expect(map.transitiveExports(libPath('a.dart')), unorderedEquals([
new Export(libPath('a.dart'), libPath('b.dart')),
new Export(libPath('a.dart'), libPath('c.dart')),
new Export(libPath('a.dart'), libPath('d.dart'), hide: ['z']),
]));
});
test('resolves package: exports', () {
createLibrary('a.dart', 'export "package:b/b.dart";');
var bPath = pathos.join('packages', 'b', 'b.dart');
createLibrary(bPath);
var map = parse(['a.dart']);
expect(map.exports[libPath('a.dart')], unorderedEquals([
new Export(libPath('a.dart'), libPath(bPath))
]));
});
test('ignores dart: exports', () {
createLibrary('a.dart', 'export "dart:async";');
var map = parse(['a.dart']);
expect(map.exports[libPath('a.dart')], isEmpty);
});
test('.parse() resolves package: imports', () {
var aPath = pathos.join('packages', 'a', 'a.dart');
createLibrary(aPath, 'export "package:b/b.dart";');
var bPath = pathos.join('packages', 'b', 'b.dart');
createLibrary(bPath);
var map = new ExportMap.parse(
[Uri.parse('package:a/a.dart')],
pathos.join(tempDir, 'packages'));
expect(map.exports[libPath(aPath)], unorderedEquals([
new Export(libPath(aPath), libPath(bPath))
]));
});
test('.parse() ignores dart: imports', () {
var map = new ExportMap.parse(
[Uri.parse('dart:async')],
pathos.join(tempDir, 'packages'));
expect(map.exports, isEmpty);
});
});
group('Export', () {
test('normalizes hide and show', () {
expect(new Export('', '', show: ['x', 'y'], hide: ['y', 'z']),
equals(new Export('', '', show: ['x'])));
});
test("doesn't care about the order of show or hide", () {
expect(new Export('', '', show: ['x', 'y']),
equals(new Export('', '', show: ['y', 'x'])));
expect(new Export('', '', hide: ['x', 'y']),
equals(new Export('', '', hide: ['y', 'x'])));
});
test('with no combinators considers anything visible', () {
var export = new Export('', '');
expect(export.isMemberVisible('x'), isTrue);
expect(export.isMemberVisible('y'), isTrue);
expect(export.isMemberVisible('z'), isTrue);
});
test('with hide combinators considers anything not hidden visible', () {
var export = new Export('', '', hide: ['x', 'y']);
expect(export.isMemberVisible('x'), isFalse);
expect(export.isMemberVisible('y'), isFalse);
expect(export.isMemberVisible('z'), isTrue);
});
test('with show combinators considers anything not shown invisible', () {
var export = new Export('', '', show: ['x', 'y']);
expect(export.isMemberVisible('x'), isTrue);
expect(export.isMemberVisible('y'), isTrue);
expect(export.isMemberVisible('z'), isFalse);
});
test('composing uses the parent exporter and child path', () {
expect(new Export('exporter1.dart', 'path1.dart')
.compose(new Export('exporter2.dart', 'path2.dart')),
equals(new Export('exporter1.dart', 'path2.dart')));
});
test('composing show . show takes the intersection', () {
expect(new Export('', '', show: ['x', 'y'])
.compose(new Export('', '', show: ['y', 'z'])),
equals(new Export('', '', show: ['y'])));
});
test('composing show . hide takes the difference', () {
expect(new Export('', '', show: ['x', 'y'])
.compose(new Export('', '', hide: ['y', 'z'])),
equals(new Export('', '', show: ['x'])));
});
test('composing hide . show takes the reverse difference', () {
expect(new Export('', '', hide: ['x', 'y'])
.compose(new Export('', '', show: ['y', 'z'])),
equals(new Export('', '', show: ['z'])));
});
test('composing hide . hide takes the union', () {
expect(new Export('', '', hide: ['x', 'y'])
.compose(new Export('', '', hide: ['y', 'z'])),
equals(new Export('', '', hide: ['x', 'y', 'z'])));
});
test('merging requires identical exporters and paths', () {
expect(() => new Export('exporter1.dart', '')
.merge(new Export('exporter2.dart', '')),
throwsA(isArgumentError));
expect(() => new Export('', 'path1.dart')
.merge(new Export('', 'path2.dart')),
throwsA(isArgumentError));
expect(new Export('', '').merge(new Export('', '')),
equals(new Export('', '')));
});
test('merging show + show takes the union', () {
expect(new Export('', '', show: ['x', 'y'])
.merge(new Export('', '', show: ['y', 'z'])),
equals(new Export('', '', show: ['x', 'y', 'z'])));
});
test('merging show + hide takes the difference', () {
expect(new Export('', '', show: ['x', 'y'])
.merge(new Export('', '', hide: ['y', 'z'])),
equals(new Export('', '', hide: ['z'])));
});
test('merging hide + show takes the difference', () {
expect(new Export('', '', hide: ['x', 'y'])
.merge(new Export('', '', show: ['y', 'z'])),
equals(new Export('', '', hide: ['x'])));
});
test('merging hide + hide takes the intersection', () {
expect(new Export('', '', hide: ['x', 'y'])
.merge(new Export('', '', hide: ['y', 'z'])),
equals(new Export('', '', hide: ['y'])));
});
});
}
ExportMap parse(List<String> libraries) {
return new ExportMap.parse(
libraries.map(libPath)
.map(pathToFileUri),
pathos.join(tempDir, 'packages'));
}
void createLibrary(String name, [String contents]) {
if (contents == null) contents = '';
new Directory(pathos.dirname(libPath(name))).createSync(recursive: true);
new File(libPath(name)).writeAsStringSync('''
library ${pathos.basename(name)};
$contents
''');
}
String libPath(String name) => pathos.normalize(pathos.join(tempDir, name));
void createTempDir() {
tempDir = new Directory('').createTempSync().path;
new Directory(pathos.join(tempDir, 'packages')).createSync();
}
void deleteTempDir() {
new Directory(tempDir).deleteSync(recursive: true);
}

View file

@ -0,0 +1,45 @@
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:unittest/unittest.dart';
import '../lib/src/dartdoc/utils.dart';
void main() {
group('countOccurrences', () {
test('empty text returns 0', () {
expect(countOccurrences('', 'needle'), equals(0));
});
test('one occurrence', () {
expect(countOccurrences('bananarama', 'nara'), equals(1));
});
test('multiple occurrences', () {
expect(countOccurrences('bananarama', 'a'), equals(5));
});
test('overlapping matches do not count', () {
expect(countOccurrences('bananarama', 'ana'), equals(1));
});
});
group('repeat', () {
test('zero times returns an empty string', () {
expect(repeat('ba', 0), isEmpty);
});
test('one time returns the string', () {
expect(repeat('ba', 1), equals('ba'));
});
test('multiple times', () {
expect(repeat('ba', 3), equals('bababa'));
});
test('multiple times with a separator', () {
expect(repeat('ba', 3, separator: ' '), equals('ba ba ba'));
});
});
}

View file

@ -17,6 +17,7 @@ library apidoc;
import 'dart:async';
import 'dart:io';
import 'dart:json' as json;
import 'dart:uri';
import 'html_diff.dart';
@ -25,6 +26,7 @@ import '../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors.dart';
import '../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors_util.dart';
import '../../sdk/lib/_internal/dartdoc/lib/dartdoc.dart';
import '../../sdk/lib/_internal/libraries.dart';
import 'package:pathos/path.dart' as pathos;
HtmlDiff _diff;
@ -37,7 +39,7 @@ void main() {
List<String> excludedLibraries = <String>[];
List<String> includedLibraries = <String>[];
Path packageRoot;
String packageRoot;
String version;
// Parse the command-line arguments.
@ -65,7 +67,7 @@ void main() {
} else if (arg.startsWith('--out=')) {
outputDir = new Path(arg.substring('--out='.length));
} else if (arg.startsWith('--package-root=')) {
packageRoot = new Path(arg.substring('--package-root='.length));
packageRoot = arg.substring('--package-root='.length);
} else if (arg.startsWith('--version=')) {
version = arg.substring('--version='.length);
} else {
@ -104,10 +106,10 @@ void main() {
// TODO(johnniwinther): Libraries for the compilation seem to be more like
// URIs. Perhaps Path should have a toURI() method.
// Add all of the core libraries.
final apidocLibraries = <Path>[];
final apidocLibraries = <Uri>[];
LIBRARIES.forEach((String name, LibraryInfo info) {
if (info.documented) {
apidocLibraries.add(new Path('dart:$name'));
apidocLibraries.add(Uri.parse('dart:$name'));
}
});
@ -130,7 +132,7 @@ void main() {
}
if (new File.fromPath(libPath).existsSync()) {
apidocLibraries.add(libPath);
apidocLibraries.add(_pathToFileUri(libPath.toNativePath()));
includedLibraries.add(libName);
} else {
print('Warning: could not find package at $path');
@ -147,7 +149,7 @@ void main() {
// TODO(amouravski): make apidoc use roughly the same flow as bin/dartdoc.
Future.wait([copiedStatic, copiedApiDocStatic, htmlDiff])
.then((_) => apidoc.documentLibraries( apidocLibraries, libPath,
.then((_) => apidoc.documentLibraries(apidocLibraries, libPath,
packageRoot))
.then((_) => compileScript(mode, outputDir, libPath))
.then((_) => print(apidoc.status))
@ -447,3 +449,14 @@ class Apidoc extends Dartdoc {
return a(memberUrl(member), memberName);
}
}
/** Converts a local path string to a `file:` [Uri]. */
Uri _pathToFileUri(String path) {
path = pathos.absolute(path);
if (Platform.operatingSystem != 'windows') {
return Uri.parse('file://$path');
} else {
return Uri.parse('file:///${path.replaceAll("\\", "/")}');
}
}