diff --git a/bin/dartdoc.dart b/bin/dartdoc.dart index f73248a22a..83b3f6180f 100644 --- a/bin/dartdoc.dart +++ b/bin/dartdoc.dart @@ -17,7 +17,9 @@ Future<void> main(List<String> arguments) async { // There was an error while parsing options. return; } - final packageBuilder = PubPackageBuilder(config, pubPackageMetaProvider); + final packageConfigProvider = PhysicalPackageConfigProvider(); + final packageBuilder = + PubPackageBuilder(config, pubPackageMetaProvider, packageConfigProvider); final dartdoc = config.generateDocs ? await Dartdoc.fromContext(config, packageBuilder) : await Dartdoc.withEmptyGenerator(config, packageBuilder); diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart index 016304fedb..63d09b1a05 100644 --- a/lib/dartdoc.dart +++ b/lib/dartdoc.dart @@ -33,6 +33,7 @@ export 'package:dartdoc/src/dartdoc_options.dart'; export 'package:dartdoc/src/element_type.dart'; export 'package:dartdoc/src/generator/generator.dart'; export 'package:dartdoc/src/model/model.dart'; +export 'package:dartdoc/src/package_config_provider.dart'; export 'package:dartdoc/src/package_meta.dart'; const String programName = 'dartdoc'; diff --git a/lib/src/dartdoc_options.dart b/lib/src/dartdoc_options.dart index a2e07cc8fd..cfb1ee94a7 100644 --- a/lib/src/dartdoc_options.dart +++ b/lib/src/dartdoc_options.dart @@ -669,8 +669,8 @@ abstract class DartdocOption<T> { String get _directoryCurrentPath => resourceProvider.pathContext.current; /// Calls [valueAt] on the directory this element is defined in. - T valueAtElement(Element element) => valueAt(resourceProvider.getFolder( - resourceProvider.pathContext.canonicalize( + T valueAtElement(Element element) => + valueAt(resourceProvider.getFolder(resourceProvider.pathContext.normalize( resourceProvider.pathContext.basename(element.source.fullName)))); /// Adds a DartdocOption to the children of this DartdocOption. @@ -1683,7 +1683,7 @@ Future<List<DartdocOption<Object>>> createDartdocOptions( return resourceProvider.pathContext .join(flutterRoot, 'bin', 'cache', 'dart-sdk'); } - return resourceProvider.defaultSdkDir.path; + return packageMetaProvider.defaultSdkDir.path; }, packageMetaProvider.resourceProvider, help: 'Path to the SDK directory.', isDir: true, mustExist: true), DartdocOptionArgFile<bool>( diff --git a/lib/src/io_utils.dart b/lib/src/io_utils.dart index c4a3cfaf18..27fcbeaa26 100644 --- a/lib/src/io_utils.dart +++ b/lib/src/io_utils.dart @@ -11,7 +11,8 @@ import 'dart:io' as io; import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/file_system/physical_file_system.dart'; -import 'package:dartdoc/src/package_meta.dart'; +import 'package:analyzer/src/generated/sdk.dart'; +import 'package:analyzer/src/test_utilities/mock_sdk.dart'; import 'package:path/path.dart' as path; Encoding utf8AllowMalformed = Utf8Codec(allowMalformed: true); @@ -34,6 +35,14 @@ String resolveTildePath(String originalPath) { return path.join(homeDir, originalPath.substring(2)); } +bool isSdkLibraryDocumented(SdkLibrary library) { + if (library is MockSdkLibrary) { + // Not implemented in [MockSdkLibrary]. + return true; + } + return library.isDocumented; +} + extension ResourceProviderExtensions on ResourceProvider { Folder createSystemTemp(String prefix) { if (this is PhysicalResourceProvider) { @@ -43,20 +52,6 @@ extension ResourceProviderExtensions on ResourceProvider { } } - Folder get defaultSdkDir { - if (this is PhysicalResourceProvider) { - var sdkDir = getFile(pathContext.absolute(io.Platform.resolvedExecutable)) - .parent - .parent; - assert(pathContext.equals( - sdkDir.path, PubPackageMeta.sdkDirParent(sdkDir, this).path)); - return sdkDir; - } else { - // TODO(srawlins): Return what is needed for tests. - return null; - } - } - String get resolvedExecutable { if (this is PhysicalResourceProvider) { return io.Platform.resolvedExecutable; @@ -94,48 +89,6 @@ extension ResourceProviderExtensions on ResourceProvider { } } -/// Lists the contents of [dir]. -/// -/// If [recursive] is `true`, lists subdirectory contents (defaults to `false`). -/// -/// Excludes files and directories beginning with `.` -/// -/// The returned paths are guaranteed to begin with [dir]. -Iterable<String> listDir(String dir, - {bool recursive = false, - Iterable<io.FileSystemEntity> Function(io.Directory dir) listDir}) { - listDir ??= (io.Directory dir) => dir.listSync(); - - return _doList(dir, <String>{}, recursive, listDir); -} - -Iterable<String> _doList( - String dir, - Set<String> listedDirectories, - bool recurse, - Iterable<io.FileSystemEntity> Function(io.Directory dir) listDir) sync* { - // Avoid recursive symlinks. - var resolvedPath = io.Directory(dir).resolveSymbolicLinksSync(); - if (!listedDirectories.contains(resolvedPath)) { - listedDirectories = Set<String>.from(listedDirectories); - listedDirectories.add(resolvedPath); - - for (var entity in listDir(io.Directory(dir))) { - // Skip hidden files and directories - if (path.basename(entity.path).startsWith('.')) { - continue; - } - - yield entity.path; - if (entity is io.Directory) { - if (recurse) { - yield* _doList(entity.path, listedDirectories, recurse, listDir); - } - } - } - } -} - /// Converts `.` and `:` into `-`, adding a ".html" extension. /// /// For example: diff --git a/lib/src/model/library.dart b/lib/src/model/library.dart index d6f2a43b6c..2b7a0c1640 100644 --- a/lib/src/model/library.dart +++ b/lib/src/model/library.dart @@ -10,6 +10,7 @@ import 'package:analyzer/dart/element/visitor.dart'; import 'package:analyzer/source/line_info.dart'; import 'package:analyzer/src/dart/element/inheritance_manager3.dart'; import 'package:analyzer/src/generated/sdk.dart'; +import 'package:dartdoc/src/io_utils.dart'; import 'package:dartdoc/src/model/model.dart'; import 'package:dartdoc/src/package_meta.dart' show PackageMeta; import 'package:dartdoc/src/quiver.dart' as quiver; @@ -212,7 +213,8 @@ class Library extends ModelElement with Categorization, TopLevelContainer { @override bool get isPublic { if (!super.isPublic) return false; - if (sdkLib != null && (sdkLib.isInternal || !sdkLib.isDocumented)) { + if (sdkLib != null && + (sdkLib.isInternal || !isSdkLibraryDocumented(sdkLib))) { return false; } if (config.isLibraryExcluded(name) || diff --git a/lib/src/model/package.dart b/lib/src/model/package.dart index a8053e517a..c5a4fafcba 100644 --- a/lib/src/model/package.dart +++ b/lib/src/model/package.dart @@ -12,7 +12,9 @@ import 'package:dartdoc/src/warnings.dart'; import 'package:path/path.dart' as path; import 'package:pub_semver/pub_semver.dart'; -final RegExp substituteNameVersion = RegExp(r'%([bnv])%'); +@Deprecated('Public variable intended to be private; will be removed as early ' + 'as Dartdoc 1.0.0') +RegExp get substituteNameVersion => Package._substituteNameVersion; // All hrefs are emitted as relative paths from the output root. We are unable // to compute them from the page we are generating, and many properties computed @@ -224,51 +226,56 @@ class Package extends LibraryContainer String _baseHref; String get baseHref { - if (_baseHref == null) { - if (documentedWhere == DocumentLocation.remote) { - _baseHref = - config.linkToUrl.replaceAllMapped(substituteNameVersion, (m) { - switch (m.group(1)) { - // Return the prerelease tag of the release if a prerelease, - // or 'stable' otherwise. Mostly coded around - // the Dart SDK's use of dev/stable, but theoretically applicable - // elsewhere. - case 'b': - { - var version = Version.parse(packageMeta.version); - var tag = 'stable'; - if (version.isPreRelease) { - // version.preRelease is a List<dynamic> with a mix of - // integers and strings. Given this, handle - // 2.8.0-dev.1.0, 2.9.0-1.0.dev, and similar - // variations. - tag = version.preRelease.whereType<String>().first; - // Who knows about non-SDK packages, but assert that SDKs - // must conform to the known format. - assert( - packageMeta.isSdk == false || int.tryParse(tag) == null, - 'Got an integer as string instead of the expected "dev" tag'); - } - return tag; - } - case 'n': - return name; - // The full version string of the package. - case 'v': - return packageMeta.version; - default: - assert(false, 'Unsupported case: ${m.group(1)}'); - return null; - } - }); - if (!_baseHref.endsWith('/')) _baseHref = '${_baseHref}/'; - } else { - _baseHref = config.useBaseHref ? '' : HTMLBASE_PLACEHOLDER; - } + if (_baseHref != null) { + return _baseHref; } + + if (documentedWhere == DocumentLocation.remote) { + _baseHref = _remoteBaseHref; + if (!_baseHref.endsWith('/')) _baseHref = '${_baseHref}/'; + } else { + _baseHref = config.useBaseHref ? '' : HTMLBASE_PLACEHOLDER; + } + return _baseHref; } + String get _remoteBaseHref { + return config.linkToUrl.replaceAllMapped(_substituteNameVersion, (m) { + switch (m.group(1)) { + // Return the prerelease tag of the release if a prerelease, or 'stable' + // otherwise. Mostly coded around the Dart SDK's use of dev/stable, but + // theoretically applicable elsewhere. + case 'b': + { + var version = Version.parse(packageMeta.version); + var tag = 'stable'; + if (version.isPreRelease) { + // `version.preRelease` is a `List<dynamic>` with a mix of + // integers and strings. Given this, handle + // "2.8.0-dev.1.0, 2.9.0-1.0.dev", and similar variations. + tag = version.preRelease.whereType<String>().first; + // Who knows about non-SDK packages, but SDKs must conform to the + // known format. + assert(packageMeta.isSdk == false || int.tryParse(tag) == null, + 'Got an integer as string instead of the expected "dev" tag'); + } + return tag; + } + case 'n': + return name; + // The full version string of the package. + case 'v': + return packageMeta.version; + default: + assert(false, 'Unsupported case: ${m.group(1)}'); + return null; + } + }); + } + + static final _substituteNameVersion = RegExp(r'%([bnv])%'); + @override String get href => '$baseHref$filePath'; diff --git a/lib/src/model/package_builder.dart b/lib/src/model/package_builder.dart index 57b863799a..54519d9297 100644 --- a/lib/src/model/package_builder.dart +++ b/lib/src/model/package_builder.dart @@ -3,14 +3,13 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; -import 'dart:io'; import 'package:analyzer/dart/analysis/features.dart'; import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/element/element.dart'; -import 'package:analyzer/file_system/file_system.dart' as file_system; -import 'package:analyzer/file_system/physical_file_system.dart'; +import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/src/context/builder.dart'; +import 'package:analyzer/src/context/packages.dart'; import 'package:analyzer/src/dart/analysis/byte_store.dart'; import 'package:analyzer/src/dart/analysis/driver.dart'; import 'package:analyzer/src/dart/analysis/file_state.dart'; @@ -21,18 +20,17 @@ import 'package:analyzer/src/generated/java_io.dart'; import 'package:analyzer/src/generated/sdk.dart'; import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/generated/source_io.dart'; -import 'package:dartdoc/src/quiver.dart' as quiver; import 'package:analyzer/src/source/package_map_resolver.dart'; import 'package:dartdoc/src/dartdoc_options.dart'; -import 'package:dartdoc/src/io_utils.dart'; import 'package:dartdoc/src/logging.dart'; import 'package:dartdoc/src/model/model.dart'; +import 'package:dartdoc/src/quiver.dart' as quiver; +import 'package:dartdoc/src/package_config_provider.dart'; import 'package:dartdoc/src/package_meta.dart' show PackageMeta, PackageMetaProvider; import 'package:dartdoc/src/render/renderer_factory.dart'; import 'package:dartdoc/src/special_elements.dart'; import 'package:meta/meta.dart'; -import 'package:package_config/package_config.dart' show findPackageConfig; import 'package:path/path.dart' as path; /// Everything you need to instantiate a PackageGraph object for documenting. @@ -45,8 +43,10 @@ abstract class PackageBuilder { class PubPackageBuilder implements PackageBuilder { final DartdocOptionContext config; final PackageMetaProvider packageMetaProvider; + final PackageConfigProvider packageConfigProvider; - PubPackageBuilder(this.config, this.packageMetaProvider); + PubPackageBuilder( + this.config, this.packageMetaProvider, this.packageConfigProvider); @override Future<PackageGraph> buildPackageGraph() async { @@ -78,11 +78,13 @@ class PubPackageBuilder implements PackageBuilder { return newGraph; } - FolderBasedDartSdk _sdk; + /*late final*/ DartSdk _sdk; + + DartSdk get sdk { + _sdk ??= packageMetaProvider.defaultSdk ?? + FolderBasedDartSdk( + resourceProvider, resourceProvider.getFolder(config.sdkDir)); - FolderBasedDartSdk get sdk { - _sdk ??= FolderBasedDartSdk(PhysicalResourceProvider.INSTANCE, - PhysicalResourceProvider.INSTANCE.getFolder(config.sdkDir)); return _sdk; } @@ -90,30 +92,32 @@ class PubPackageBuilder implements PackageBuilder { EmbedderSdk get embedderSdk { if (_embedderSdk == null && !config.topLevelPackageMeta.isSdk) { - _embedderSdk = EmbedderSdk(PhysicalResourceProvider.INSTANCE, - EmbedderYamlLocator(_packageMap).embedderYamls); + _embedderSdk = EmbedderSdk( + resourceProvider, EmbedderYamlLocator(_packageMap).embedderYamls); } return _embedderSdk; } + ResourceProvider get resourceProvider => packageMetaProvider.resourceProvider; + Future<void> _calculatePackageMap() async { assert(_packageMap == null); - _packageMap = <String, List<file_system.Folder>>{}; - file_system.Folder cwd = - PhysicalResourceProvider.INSTANCE.getResource(config.inputDir); - var info = await findPackageConfig(Directory(cwd.path)); + _packageMap = <String, List<Folder>>{}; + Folder cwd = resourceProvider.getResource(config.inputDir); + var info = await packageConfigProvider + .findPackageConfig(resourceProvider.getFolder(cwd.path)); if (info == null) return; for (var package in info.packages) { var packagePath = path.normalize(path.fromUri(package.packageUriRoot)); - var resource = PhysicalResourceProvider.INSTANCE.getResource(packagePath); - if (resource is file_system.Folder) { + var resource = resourceProvider.getResource(packagePath); + if (resource is Folder) { _packageMap[package.name] = [resource]; } } } - /*late final*/ Map<String, List<file_system.Folder>> _packageMap; + /*late final*/ Map<String, List<Folder>> _packageMap; DartUriResolver _embedderResolver; @@ -123,9 +127,8 @@ class PubPackageBuilder implements PackageBuilder { } SourceFactory get sourceFactory { - var resolvers = <UriResolver>[]; final UriResolver packageResolver = - PackageMapUriResolver(PhysicalResourceProvider.INSTANCE, _packageMap); + PackageMapUriResolver(resourceProvider, _packageMap); UriResolver sdkResolver; if (embedderSdk == null || embedderSdk.urlMappings.isEmpty) { // The embedder uri resolver has no mappings. Use the default Dart SDK @@ -141,15 +144,15 @@ class PubPackageBuilder implements PackageBuilder { /// never resolve to embedded SDK files, and the resolvers list must still /// contain a DartUriResolver. This hack won't be necessary once analyzer /// has a clean public API. - resolvers.add(PackageWithoutSdkResolver(packageResolver, sdkResolver)); - resolvers.add(sdkResolver); - resolvers.add( - file_system.ResourceUriResolver(PhysicalResourceProvider.INSTANCE)); + var resolvers = [ + PackageWithoutSdkResolver(packageResolver, sdkResolver), + sdkResolver, + ResourceUriResolver(resourceProvider), + ]; assert( resolvers.any((UriResolver resolver) => resolver is DartUriResolver)); - var sourceFactory = SourceFactory(resolvers); - return sourceFactory; + return SourceFactory(resolvers); } AnalysisDriver _driver; @@ -166,15 +169,9 @@ class PubPackageBuilder implements PackageBuilder { // TODO(jcollins-g): Make use of currently not existing API for managing // many AnalysisDrivers // TODO(jcollins-g): make use of DartProject isApi() - _driver = AnalysisDriver( - scheduler, - log, - PhysicalResourceProvider.INSTANCE, - MemoryByteStore(), - FileContentOverlay(), - null, - sourceFactory, - options); + _driver = AnalysisDriver(scheduler, log, resourceProvider, + MemoryByteStore(), FileContentOverlay(), null, sourceFactory, options, + packages: Packages.empty); driver.results.listen((_) => logProgress('')); driver.exceptions.listen((_) {}); scheduler.start(); @@ -200,7 +197,9 @@ class PubPackageBuilder implements PackageBuilder { if (name.startsWith(directoryCurrentPath)) { name = name.substring(directoryCurrentPath.length); - if (name.startsWith(Platform.pathSeparator)) name = name.substring(1); + if (name.startsWith(resourceProvider.pathContext.separator)) { + name = name.substring(1); + } } var javaFile = JavaFile(filePath).getAbsoluteFile(); Source source = FileBasedSource(javaFile); @@ -305,7 +304,8 @@ class PubPackageBuilder implements PackageBuilder { var packageDirs = {basePackageDir}; if (autoIncludeDependencies) { - var info = await findPackageConfig(Directory(basePackageDir)); + var info = await packageConfigProvider + .findPackageConfig(resourceProvider.getFolder(basePackageDir)); for (var package in info.packages) { if (!filterExcludes || !config.exclude.contains(package.name)) { packageDirs.add( @@ -322,7 +322,7 @@ class PubPackageBuilder implements PackageBuilder { // containing '/packages' will be added. The only exception is if the file // to analyze already has a '/package' in its path. for (var lib - in listDir(packageDir, recursive: true, listDir: _packageDirList)) { + in _listDir(packageDir, recursive: true, listDir: _packageDirList)) { if (lib.endsWith('.dart') && (!lib.contains('${sep}packages${sep}') || packageDir.contains('${sep}packages${sep}'))) { @@ -330,7 +330,7 @@ class PubPackageBuilder implements PackageBuilder { if (path.isWithin(packageLibDir, lib) && !path.isWithin(packageLibSrcDir, lib)) { // Only add the file if it does not contain 'part of'. - var contents = File(lib).readAsStringSync(); + var contents = resourceProvider.getFile(lib).readAsStringSync(); if (contents.startsWith('part of ') || contents.contains('\npart of ')) { @@ -344,6 +344,44 @@ class PubPackageBuilder implements PackageBuilder { } } + /// Lists the contents of [dir]. + /// + /// If [recursive] is `true`, lists subdirectory contents (defaults to `false`). + /// + /// Excludes files and directories beginning with `.` + /// + /// The returned paths are guaranteed to begin with [dir]. + Iterable<String> _listDir(String dir, + {bool recursive = false, + Iterable<Resource> Function(Folder dir) listDir}) { + listDir ??= (Folder dir) => dir.getChildren(); + + return _doList(dir, <String>{}, recursive, listDir); + } + + Iterable<String> _doList(String dir, Set<String> listedDirectories, + bool recurse, Iterable<Resource> Function(Folder dir) listDir) sync* { + // Avoid recursive symlinks. + var resolvedPath = + resourceProvider.getFolder(dir).resolveSymbolicLinksSync().path; + if (!listedDirectories.contains(resolvedPath)) { + listedDirectories = Set<String>.from(listedDirectories); + listedDirectories.add(resolvedPath); + + for (var resource in listDir(resourceProvider.getFolder(dir))) { + // Skip hidden files and directories + if (path.basename(resource.path).startsWith('.')) { + continue; + } + + yield resource.path; + if (resource is Folder && recurse) { + yield* _doList(resource.path, listedDirectories, recurse, listDir); + } + } + } + } + /// Calculate includeExternals based on a list of files. Assumes each /// file might be part of a [DartdocOptionContext], and loads those /// objects to find any [DartdocOptionContext.includeExternal] configurations @@ -369,7 +407,8 @@ class PubPackageBuilder implements PackageBuilder { } files = quiver.concat([files, _includeExternalsFrom(files)]); return { - ...files.map((s) => File(s).absolute.path), + ...files.map((s) => resourceProvider.pathContext + .absolute(resourceProvider.getFile(s).path)), ...getEmbedderSdkFiles(), }; } @@ -377,7 +416,9 @@ class PubPackageBuilder implements PackageBuilder { Iterable<String> getEmbedderSdkFiles() { return [ for (var dartUri in _embedderSdkUris) - File(embedderSdk.mapDartUri(dartUri).fullName).absolute.path + resourceProvider.pathContext.absolute(resourceProvider + .getFile(embedderSdk.mapDartUri(dartUri).fullName) + .path), ]; } @@ -390,12 +431,12 @@ class PubPackageBuilder implements PackageBuilder { } Future<void> getLibraries(PackageGraph uninitializedPackageGraph) async { - DartSdk findSpecialsSdk = sdk; + var findSpecialsSdk = sdk; if (embedderSdk != null && embedderSdk.urlMappings.isNotEmpty) { findSpecialsSdk = embedderSdk; } var files = await _getFiles(); - var specialFiles = specialLibraryFiles(findSpecialsSdk).toSet(); + var specialFiles = specialLibraryFiles(findSpecialsSdk); /// Returns true if this library element should be included according /// to the configuration. @@ -429,21 +470,21 @@ class PubPackageBuilder implements PackageBuilder { /// it like a package and only return the `lib` dir. /// /// This ensures that packages don't have non-`lib` content documented. - static Iterable<FileSystemEntity> _packageDirList(Directory dir) sync* { - var entities = dir.listSync(); + static Iterable<Resource> _packageDirList(Folder dir) sync* { + var resources = dir.getChildren(); - var pubspec = entities.firstWhere( + var pubspec = resources.firstWhere( (e) => e is File && path.basename(e.path) == 'pubspec.yaml', orElse: () => null); - var libDir = entities.firstWhere( - (e) => e is Directory && path.basename(e.path) == 'lib', + var libDir = resources.firstWhere( + (e) => e is Folder && path.basename(e.path) == 'lib', orElse: () => null); if (pubspec != null && libDir != null) { yield libDir; } else { - yield* entities; + yield* resources; } } } diff --git a/lib/src/package_config_provider.dart b/lib/src/package_config_provider.dart new file mode 100644 index 0000000000..259ed379cf --- /dev/null +++ b/lib/src/package_config_provider.dart @@ -0,0 +1,38 @@ +// Copyright (c) 2020, 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:async'; +import 'dart:io' as io; + +import 'package:analyzer/file_system/file_system.dart'; +import 'package:package_config/package_config.dart' as package_config; + +/// A provider of PackageConfig-finding methods. +/// +/// This provides an abstraction around package_config, which can only work +/// with the physical file system. +abstract class PackageConfigProvider { + Future<package_config.PackageConfig> findPackageConfig(Folder dir); +} + +class PhysicalPackageConfigProvider implements PackageConfigProvider { + @override + Future<package_config.PackageConfig> findPackageConfig(Folder dir) => + package_config.findPackageConfig(io.Directory(dir.path)); +} + +class FakePackageConfigProvider implements PackageConfigProvider { + /// A mapping of package config search locations to configured packages. + final _packageConfigData = <String, List<package_config.Package>>{}; + + void addPackageToConfigFor(String location, String name, Uri root) { + _packageConfigData.putIfAbsent(location, () => []); + _packageConfigData[location].add(package_config.Package(name, root)); + } + + @override + Future<package_config.PackageConfig> findPackageConfig(Folder dir) async { + return package_config.PackageConfig(_packageConfigData[dir.path]); + } +} diff --git a/lib/src/package_meta.dart b/lib/src/package_meta.dart index e0b6f27b32..369782f61b 100644 --- a/lib/src/package_meta.dart +++ b/lib/src/package_meta.dart @@ -10,6 +10,7 @@ import 'dart:io' show Platform, Process; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/file_system/physical_file_system.dart'; +import 'package:analyzer/src/generated/sdk.dart'; import 'package:dartdoc/dartdoc.dart'; import 'package:path/path.dart' as p; import 'package:yaml/yaml.dart'; @@ -33,11 +34,15 @@ final List<List<String>> __sdkDirFilePathsPosix = [ ]; final PackageMetaProvider pubPackageMetaProvider = PackageMetaProvider( - PubPackageMeta.fromElement, - PubPackageMeta.fromFilename, - PubPackageMeta.fromDir, - PhysicalResourceProvider.INSTANCE, -); + PubPackageMeta.fromElement, + PubPackageMeta.fromFilename, + PubPackageMeta.fromDir, + PhysicalResourceProvider.INSTANCE, + PhysicalResourceProvider.INSTANCE + .getFile(PhysicalResourceProvider.INSTANCE.pathContext + .absolute(Platform.resolvedExecutable)) + .parent + .parent); /// Sets the supported way of constructing [PackageMeta] objects. /// @@ -49,6 +54,8 @@ final PackageMetaProvider pubPackageMetaProvider = PackageMetaProvider( /// [PackageMeta] objects is built. class PackageMetaProvider { final ResourceProvider resourceProvider; + final Folder defaultSdkDir; + final DartSdk defaultSdk; final PackageMeta Function(LibraryElement, String, ResourceProvider) _fromElement; @@ -61,7 +68,8 @@ class PackageMetaProvider { PackageMeta fromDir(Folder dir) => _fromDir(dir, resourceProvider); PackageMetaProvider(this._fromElement, this._fromFilename, this._fromDir, - this.resourceProvider); + this.resourceProvider, this.defaultSdkDir, + {this.defaultSdk}); } /// Describes a single package in the context of `dartdoc`. @@ -91,9 +99,10 @@ abstract class PackageMeta { p.Context get pathContext => resourceProvider.pathContext; - /// Returns true if this represents a 'Dart' SDK. A package can be part of - /// Dart and Flutter at the same time, but if we are part of a Dart SDK - /// sdkType should never return null. + /// Returns true if this represents a 'Dart' SDK. + /// + /// A package can be part of Dart and Flutter at the same time, but if we are + /// part of a Dart SDK, [sdkType] should never return null. bool get isSdk; /// Returns 'Dart' or 'Flutter' (preferentially, 'Flutter' when the answer is @@ -152,11 +161,10 @@ abstract class PubPackageMeta extends PackageMeta { __sdkDirFilePaths = []; if (Platform.isWindows) { for (var paths in __sdkDirFilePathsPosix) { - var windowsPaths = <String>[]; - for (var path in paths) { - windowsPaths - .add(p.joinAll(p.Context(style: p.Style.posix).split(path))); - } + var windowsPaths = [ + for (var path in paths) + p.joinAll(p.Context(style: p.Style.posix).split(path)), + ]; __sdkDirFilePaths.add(windowsPaths); } } else { @@ -166,19 +174,19 @@ abstract class PubPackageMeta extends PackageMeta { return __sdkDirFilePaths; } - /// Returns the directory of the SDK if the given directory is inside a Dart - /// SDK. Returns null if the directory isn't a subdirectory of the SDK. static final _sdkDirParent = <String, Folder>{}; + /// If [dir] is inside a Dart SDK, returns the directory of the SDK, and `null` + /// otherwise. static Folder sdkDirParent(Folder dir, ResourceProvider resourceProvider) { - var dirPathCanonical = p.canonicalize(dir.path); + var pathContext = resourceProvider.pathContext; + var dirPathCanonical = pathContext.canonicalize(dir.path); if (!_sdkDirParent.containsKey(dirPathCanonical)) { _sdkDirParent[dirPathCanonical] = null; while (dir.exists) { if (_sdkDirFilePaths.every((List<String> l) { - return l.any((f) => resourceProvider - .getFile(resourceProvider.pathContext.join(dir.path, f)) - .exists); + return l.any((f) => + resourceProvider.getFile(pathContext.join(dir.path, f)).exists); })) { _sdkDirParent[dirPathCanonical] = dir; break; diff --git a/test/comment_processable_test.dart b/test/comment_processable_test.dart index 1be4c2d7bd..72a1b13f93 100644 --- a/test/comment_processable_test.dart +++ b/test/comment_processable_test.dart @@ -6,6 +6,7 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/file_system/memory_file_system.dart'; import 'package:analyzer/src/generated/source.dart'; +import 'package:analyzer/src/test_utilities/mock_sdk.dart'; import 'package:dartdoc/src/dartdoc_options.dart'; import 'package:dartdoc/src/model/model.dart'; import 'package:dartdoc/src/package_meta.dart'; @@ -42,6 +43,7 @@ name: foo PubPackageMeta.fromFilename, PubPackageMeta.fromDir, resourceProvider, + resourceProvider.getFolder(resourceProvider.convertPath(sdkRoot)), ); var optionSet = await DartdocOptionSet.fromOptionGenerators( 'dartdoc', [createDartdocOptions], packageMetaProvider); diff --git a/test/end2end/dartdoc_test.dart b/test/end2end/dartdoc_test.dart index e4246cae7d..fbd858ade9 100644 --- a/test/end2end/dartdoc_test.dart +++ b/test/end2end/dartdoc_test.dart @@ -31,8 +31,6 @@ final Folder _testPackageBadDir = _getFolder('testing/test_package_bad'); final Folder _testPackageMinimumDir = _getFolder('testing/test_package_minimum'); final Folder _testSkyEnginePackage = _getFolder('testing/sky_engine'); -final Folder _testPackageWithNoReadme = - _getFolder('testing/test_package_small'); final Folder _testPackageIncludeExclude = _getFolder('testing/test_package_include_exclude'); final Folder _testPackageImportExportError = @@ -97,7 +95,8 @@ void main() { return await Dartdoc.fromContext( context, - PubPackageBuilder(context, pubPackageMetaProvider), + PubPackageBuilder( + context, pubPackageMetaProvider, PhysicalPackageConfigProvider()), ); } @@ -331,19 +330,6 @@ void main() { skip: 'Blocked on getting analysis errors with correct interpretation' 'from analysis_options'); - test('generate docs for a package that does not have a readme', () async { - var dartdoc = await buildDartdoc([], _testPackageWithNoReadme, tempDir); - - var results = await dartdoc.generateDocs(); - expect(results.packageGraph, isNotNull); - - var p = results.packageGraph; - expect(p.defaultPackage.name, 'test_package_small'); - expect(p.defaultPackage.hasHomepage, isFalse); - expect(p.defaultPackage.hasDocumentationFile, isFalse); - expect(p.localPublicLibraries, hasLength(1)); - }); - test('generate docs including a single library', () async { var dartdoc = await buildDartdoc(['--include', 'fake'], _testPackageDir, tempDir); diff --git a/test/end2end/html_generator_test.dart b/test/end2end/html_generator_test.dart index fa5059bec3..bd8bf71897 100644 --- a/test/end2end/html_generator_test.dart +++ b/test/end2end/html_generator_test.dart @@ -12,6 +12,7 @@ import 'package:dartdoc/src/generator/html_resources.g.dart'; import 'package:dartdoc/src/generator/templates.dart'; import 'package:dartdoc/src/model/package_graph.dart'; import 'package:dartdoc/src/io_utils.dart'; +import 'package:dartdoc/src/package_config_provider.dart'; import 'package:dartdoc/src/warnings.dart'; import 'package:path/path.dart' as path; import 'package:test/test.dart'; @@ -119,7 +120,10 @@ void main() { setUp(() async { generator = await _initGeneratorForTest(); packageGraph = await utils.bootBasicPackage( - testPackageDuplicateDir.path, [], pubPackageMetaProvider); + testPackageDuplicateDir.path, + [], + pubPackageMetaProvider, + PhysicalPackageConfigProvider()); tempOutput = await resourceProvider.createSystemTemp('doc_test_temp'); writer = DartdocFileWriter(tempOutput.path, resourceProvider); }); diff --git a/test/end2end/model_special_cases_test.dart b/test/end2end/model_special_cases_test.dart index ac6d4bb445..ee3d731b7a 100644 --- a/test/end2end/model_special_cases_test.dart +++ b/test/end2end/model_special_cases_test.dart @@ -12,8 +12,8 @@ import 'dart:io'; import 'package:async/async.dart'; import 'package:dartdoc/dartdoc.dart'; -import 'package:dartdoc/src/io_utils.dart'; import 'package:dartdoc/src/model/model.dart'; +import 'package:dartdoc/src/package_config_provider.dart'; import 'package:dartdoc/src/package_meta.dart'; import 'package:dartdoc/src/special_elements.dart'; import 'package:pub_semver/pub_semver.dart'; @@ -27,7 +27,10 @@ final Version _platformVersion = Version.parse(_platformVersionString); final _testPackageGraphExperimentsMemo = AsyncMemoizer<PackageGraph>(); Future<PackageGraph> get _testPackageGraphExperiments => _testPackageGraphExperimentsMemo.runOnce(() => utils.bootBasicPackage( - 'testing/test_package_experiments', [], pubPackageMetaProvider, + 'testing/test_package_experiments', + [], + pubPackageMetaProvider, + PhysicalPackageConfigProvider(), additionalArguments: [ '--enable-experiment', 'non-nullable', @@ -40,33 +43,28 @@ Future<PackageGraph> get _testPackageGraphGinormous => 'testing/test_package', ['css', 'code_in_commnets', 'excluded'], pubPackageMetaProvider, + PhysicalPackageConfigProvider(), additionalArguments: [ '--auto-include-dependencies', '--no-link-to-remote' ])); -final _testPackageGraphSmallMemo = AsyncMemoizer<PackageGraph>(); -Future<PackageGraph> get _testPackageGraphSmall => - _testPackageGraphSmallMemo.runOnce(() => utils.bootBasicPackage( - 'testing/test_package_small', [], pubPackageMetaProvider, - additionalArguments: ['--no-link-to-remote'])); - final _testPackageGraphSdkMemo = AsyncMemoizer<PackageGraph>(); Future<PackageGraph> get _testPackageGraphSdk => _testPackageGraphSdkMemo.runOnce(_bootSdkPackage); Future<PackageGraph> _bootSdkPackage() async { return PubPackageBuilder( - await utils.contextFromArgv([ - '--input', - pubPackageMetaProvider.resourceProvider.defaultSdkDir.path - ], pubPackageMetaProvider), - pubPackageMetaProvider) + await utils.contextFromArgv( + ['--input', pubPackageMetaProvider.defaultSdkDir.path], + pubPackageMetaProvider), + pubPackageMetaProvider, + PhysicalPackageConfigProvider()) .buildPackageGraph(); } void main() { - var sdkDir = pubPackageMetaProvider.resourceProvider.defaultSdkDir; + var sdkDir = pubPackageMetaProvider.defaultSdkDir; if (sdkDir == null) { print('Warning: unable to locate the Dart SDK.'); @@ -265,6 +263,7 @@ void main() { 'testing/test_package', ['css', 'code_in_comments', 'excluded'], pubPackageMetaProvider, + PhysicalPackageConfigProvider(), additionalArguments: ['--inject-html']); injectionExLibrary = @@ -421,31 +420,16 @@ void main() { expect(SubForDocComments.categories.first.isDocumented, isFalse); expect(SubForDocComments.displayedCategories, isEmpty); }); - - test('Verify that packages without categories get handled', () async { - var packageGraphSmall = await _testPackageGraphSmall; - expect(packageGraphSmall.localPackages.length, equals(1)); - expect(packageGraphSmall.localPackages.first.hasCategories, isFalse); - var packageCategories = packageGraphSmall.localPackages.first.categories; - expect(packageCategories.length, equals(0)); - }, timeout: Timeout.factor(2)); }); group('Package', () { - PackageGraph ginormousPackageGraph, sdkAsPackageGraph; + PackageGraph sdkAsPackageGraph; setUpAll(() async { - ginormousPackageGraph = await _testPackageGraphGinormous; sdkAsPackageGraph = await _testPackageGraphSdk; }); group('test package', () { - test('multiple packages, sorted default', () { - expect(ginormousPackageGraph.localPackages, hasLength(5)); - expect(ginormousPackageGraph.localPackages.first.name, - equals('test_package')); - }); - test('sdk name', () { expect(sdkAsPackageGraph.defaultPackage.name, equals('Dart')); expect(sdkAsPackageGraph.defaultPackage.kind, equals('SDK')); @@ -467,16 +451,6 @@ void main() { }); }); - group('test small package', () { - test('does not have documentation', () async { - var packageGraphSmall = await _testPackageGraphSmall; - expect(packageGraphSmall.defaultPackage.hasDocumentation, isFalse); - expect(packageGraphSmall.defaultPackage.hasDocumentationFile, isFalse); - expect(packageGraphSmall.defaultPackage.documentationFile, isNull); - expect(packageGraphSmall.defaultPackage.documentation, isNull); - }); - }); - group('SDK-specific cases', () { test('Verify Interceptor is hidden from inheritance in docs', () { var htmlLibrary = sdkAsPackageGraph.libraries diff --git a/test/end2end/model_test.dart b/test/end2end/model_test.dart index 5b28c259c1..b589aeed4d 100644 --- a/test/end2end/model_test.dart +++ b/test/end2end/model_test.dart @@ -8,7 +8,6 @@ import 'dart:io'; import 'package:async/async.dart'; import 'package:dartdoc/dartdoc.dart'; -import 'package:dartdoc/src/io_utils.dart'; import 'package:dartdoc/src/model/model.dart'; import 'package:dartdoc/src/render/category_renderer.dart'; import 'package:dartdoc/src/render/enum_field_renderer.dart'; @@ -27,6 +26,7 @@ Future<PackageGraph> get _testPackageGraph => 'testing/test_package', ['css', 'code_in_comments'], pubPackageMetaProvider, + PhysicalPackageConfigProvider(), additionalArguments: ['--no-link-to-remote'])); /// For testing sort behavior. @@ -59,7 +59,7 @@ class TestLibraryContainerSdk extends TestLibraryContainer { } void main() { - var sdkDir = pubPackageMetaProvider.resourceProvider.defaultSdkDir; + var sdkDir = pubPackageMetaProvider.defaultSdkDir; if (sdkDir == null) { print('Warning: unable to locate the Dart SDK.'); diff --git a/test/package_meta_test.dart b/test/package_meta_test.dart index 377bf198e9..7339818a90 100644 --- a/test/package_meta_test.dart +++ b/test/package_meta_test.dart @@ -98,7 +98,8 @@ void main() { }); group('PackageMeta.fromSdk', () { - var p = pubPackageMetaProvider.fromDir(resourceProvider.defaultSdkDir); + var p = + pubPackageMetaProvider.fromDir(pubPackageMetaProvider.defaultSdkDir); test('has a name', () { expect(p.name, 'Dart'); diff --git a/test/package_test.dart b/test/package_test.dart new file mode 100644 index 0000000000..a11671ef7b --- /dev/null +++ b/test/package_test.dart @@ -0,0 +1,183 @@ +// Copyright (c) 2020, 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:analyzer/file_system/file_system.dart'; +import 'package:analyzer/file_system/memory_file_system.dart'; +import 'package:analyzer/src/test_utilities/mock_sdk.dart'; +import 'package:dartdoc/src/dartdoc_options.dart'; +import 'package:dartdoc/src/model/model.dart'; +import 'package:dartdoc/src/package_config_provider.dart'; +import 'package:dartdoc/src/package_meta.dart'; +import 'package:test/test.dart'; + +import 'src/utils.dart' as utils; + +void main() { + MemoryResourceProvider resourceProvider; + MockSdk mockSdk; + Folder sdkFolder; + String projectRoot; + var packageName = 'my_package'; + PackageMetaProvider packageMetaProvider; + FakePackageConfigProvider packageConfigProvider; + PackageGraph packageGraph; + + /// Dartdoc has a few indicator files it uses to verify that a directory + /// represents a Dart SDK. These include "bin/dart" and "bin/pub". + void writeSdkBinFiles(Folder root) { + var sdkBinFolder = root.getChildAssumingFolder('bin'); + sdkBinFolder.getChildAssumingFile('dart').writeAsStringSync(''); + sdkBinFolder.getChildAssumingFile('pub').writeAsStringSync(''); + } + + void writeSdk() { + mockSdk = MockSdk(resourceProvider: resourceProvider); + // The [MockSdk] only works in non-canonicalized paths, which include + // "C:\sdk", on Windows. Howerver, dartdoc works almost exclusively with + // canonical paths ("c:\sdk"). Copy all MockSdk files to the canonicalized + // path. + for (var l in mockSdk.sdkLibraries) { + var p = l.path; + resourceProvider + .getFile(resourceProvider.pathContext.canonicalize(p)) + .writeAsStringSync(resourceProvider.getFile(p).readAsStringSync()); + } + sdkFolder = resourceProvider.getFolder(resourceProvider.pathContext + .canonicalize(resourceProvider.convertPath(sdkRoot))) + ..create(); + sdkFolder.getChildAssumingFile('version').writeAsStringSync('2.9.0'); + + writeSdkBinFiles(sdkFolder); + writeSdkBinFiles( + resourceProvider.getFolder(resourceProvider.convertPath(sdkRoot))); + } + + void writePackage() { + var pathContext = resourceProvider.pathContext; + var projectsFolder = resourceProvider.getFolder( + pathContext.canonicalize(resourceProvider.convertPath('/projects'))); + var projectFolder = projectsFolder.getChildAssumingFolder(packageName) + ..create; + projectRoot = projectFolder.path; + projectFolder.getChildAssumingFile('pubspec.yaml').writeAsStringSync(''' +name: $packageName +version: 0.0.1 +'''); + projectFolder + .getChildAssumingFolder('.dart_tool') + .getChildAssumingFile('package_config.json') + .writeAsStringSync(''); + projectFolder.getChildAssumingFolder('lib').create(); + packageConfigProvider.addPackageToConfigFor( + projectRoot, packageName, Uri.file('$projectRoot/')); + } + + setUp(() async { + resourceProvider = MemoryResourceProvider(); + writeSdk(); + + packageMetaProvider = PackageMetaProvider( + PubPackageMeta.fromElement, + PubPackageMeta.fromFilename, + PubPackageMeta.fromDir, + resourceProvider, + sdkFolder, + defaultSdk: mockSdk, + ); + var optionSet = await DartdocOptionSet.fromOptionGenerators( + 'dartdoc', [createDartdocOptions], packageMetaProvider); + optionSet.parseArguments([]); + packageConfigProvider = FakePackageConfigProvider(); + }); + + test('package with no deps has 2 local packages, including SDK', () async { + writePackage(); + resourceProvider + .getFile( + resourceProvider.pathContext.join(projectRoot, 'lib', 'a.dart')) + .writeAsStringSync(''' +/// Documentation comment. +int x; +'''); + packageGraph = await utils.bootBasicPackage( + projectRoot, [], packageMetaProvider, packageConfigProvider, + additionalArguments: [ + '--auto-include-dependencies', + '--no-link-to-remote' + ]); + + var localPackages = packageGraph.localPackages; + expect(localPackages, hasLength(2)); + expect(localPackages[0].name, equals(packageName)); + expect(localPackages[1].name, equals('Dart')); + }); + + test('package with no deps has 1 local package, excluding SDK', () async { + writePackage(); + resourceProvider + .getFile( + resourceProvider.pathContext.join(projectRoot, 'lib', 'a.dart')) + .writeAsStringSync(''' +/// Documentation comment. +int x; +'''); + packageGraph = await utils.bootBasicPackage( + projectRoot, [], packageMetaProvider, packageConfigProvider, + additionalArguments: ['--no-link-to-remote']); + + var localPackages = packageGraph.localPackages; + expect(localPackages, hasLength(1)); + expect(localPackages[0].name, equals(packageName)); + }); + + test('package with no doc comments has no docs', () async { + writePackage(); + resourceProvider + .getFile( + resourceProvider.pathContext.join(projectRoot, 'lib', 'a.dart')) + .writeAsStringSync(''' +// No documentation comment. +int x; +'''); + packageGraph = await utils.bootBasicPackage( + projectRoot, [], packageMetaProvider, packageConfigProvider); + + expect(packageGraph.defaultPackage.hasDocumentation, isFalse); + expect(packageGraph.defaultPackage.hasDocumentationFile, isFalse); + expect(packageGraph.defaultPackage.documentationFile, isNull); + expect(packageGraph.defaultPackage.documentation, isNull); + }); + + test('package with no README has no homepage', () async { + writePackage(); + resourceProvider + .getFile( + resourceProvider.pathContext.join(projectRoot, 'lib', 'a.dart')) + .writeAsStringSync(''' +/// Documentation comment. +int x; +'''); + packageGraph = await utils.bootBasicPackage( + projectRoot, [], packageMetaProvider, packageConfigProvider); + + expect(packageGraph.defaultPackage.hasHomepage, isFalse); + expect(packageGraph.localPublicLibraries, hasLength(1)); + }); + + test('package with no doc comments has no categories', () async { + writePackage(); + resourceProvider + .getFile( + resourceProvider.pathContext.join(projectRoot, 'lib', 'a.dart')) + .writeAsStringSync(''' +// No documentation comment. +int x; +'''); + packageGraph = await utils.bootBasicPackage( + projectRoot, [], packageMetaProvider, packageConfigProvider); + + expect(packageGraph.localPackages.first.hasCategories, isFalse); + expect(packageGraph.localPackages.first.categories, isEmpty); + }); +} diff --git a/test/src/utils.dart b/test/src/utils.dart index 58334b29ae..6cc3109fa1 100644 --- a/test/src/utils.dart +++ b/test/src/utils.dart @@ -8,7 +8,7 @@ import 'dart:async'; import 'package:analyzer/file_system/file_system.dart'; import 'package:dartdoc/dartdoc.dart'; -import 'package:dartdoc/src/io_utils.dart'; +import 'package:dartdoc/src/package_config_provider.dart'; import 'package:dartdoc/src/package_meta.dart'; /// The number of public libraries in testing/test_package, minus 2 for @@ -36,24 +36,27 @@ Future<DartdocOptionContext> contextFromArgv( pubPackageMetaProvider.resourceProvider); } -Future<PackageGraph> bootBasicPackage(String dirPath, - List<String> excludeLibraries, PackageMetaProvider packageMetaProvider, - {List<String> additionalArguments}) async { +Future<PackageGraph> bootBasicPackage( + String dirPath, + List<String> excludeLibraries, + PackageMetaProvider packageMetaProvider, + PackageConfigProvider packageConfigProvider, + {List<String> additionalArguments = const []}) async { var resourceProvider = packageMetaProvider.resourceProvider; var dir = resourceProvider.getFolder(resourceProvider.pathContext .absolute(resourceProvider.pathContext.normalize(dirPath))); - additionalArguments ??= <String>[]; return PubPackageBuilder( await contextFromArgv([ '--input', dir.path, '--sdk-dir', - resourceProvider.defaultSdkDir.path, + packageMetaProvider.defaultSdkDir.path, '--exclude', excludeLibraries.join(','), '--allow-tools', ...additionalArguments, ], packageMetaProvider), - packageMetaProvider) + packageMetaProvider, + packageConfigProvider) .buildPackageGraph(); } diff --git a/testing/test_package_small/lib/main.dart b/testing/test_package_small/lib/main.dart deleted file mode 100644 index d7c54ac23c..0000000000 --- a/testing/test_package_small/lib/main.dart +++ /dev/null @@ -1,3 +0,0 @@ -main(List<String> args) { - print('Hello world!'); -} diff --git a/testing/test_package_small/pubspec.yaml b/testing/test_package_small/pubspec.yaml deleted file mode 100644 index a5bd444b2c..0000000000 --- a/testing/test_package_small/pubspec.yaml +++ /dev/null @@ -1,5 +0,0 @@ -name: test_package_small -version: 0.0.1 -description: A simple console application. -#dependencies: -# foo_bar: '>=1.0.0 <2.0.0'