From a6ffd670acf4000e5c8d8d6a56b2fc5ff536cbdd Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Tue, 12 Oct 2021 15:52:17 -0700 Subject: [PATCH 1/6] squash --- lib/dartdoc.dart | 506 +----------------------------- lib/src/dartdoc.dart | 515 +++++++++++++++++++++++++++++++ lib/src/model/package_graph.dart | 3 +- lib/src/model_utils.dart | 49 +-- lib/src/special_elements.dart | 10 +- 5 files changed, 548 insertions(+), 535 deletions(-) create mode 100644 lib/src/dartdoc.dart diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart index ebd0c991f2..453897966f 100644 --- a/lib/dartdoc.dart +++ b/lib/dartdoc.dart @@ -3,522 +3,18 @@ // BSD-style license that can be found in the LICENSE file. // @dart=2.9 - /// A documentation generator for Dart. /// /// Library interface is still experimental. @experimental library dartdoc; -import 'dart:async'; -import 'dart:convert'; -import 'dart:io' show Platform, exitCode, stderr; - -import 'package:analyzer/file_system/file_system.dart'; -import 'package:dartdoc/options.dart'; -import 'package:dartdoc/src/dartdoc_options.dart'; -import 'package:dartdoc/src/failure.dart'; -import 'package:dartdoc/src/generator/empty_generator.dart'; -import 'package:dartdoc/src/generator/generator.dart'; -import 'package:dartdoc/src/generator/html_generator.dart'; -import 'package:dartdoc/src/generator/markdown_generator.dart'; -import 'package:dartdoc/src/logging.dart'; -import 'package:dartdoc/src/matching_link_result.dart'; -import 'package:dartdoc/src/model/model.dart'; -import 'package:dartdoc/src/package_meta.dart'; -import 'package:dartdoc/src/tuple.dart'; -import 'package:dartdoc/src/utils.dart'; -import 'package:dartdoc/src/version.dart'; -import 'package:dartdoc/src/warnings.dart'; -import 'package:html/parser.dart' show parse; import 'package:meta/meta.dart'; -import 'package:path/path.dart' as path; +export 'package:dartdoc/src/dartdoc.dart'; 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'; -// Update when pubspec version changes by running `pub run build_runner build` -const String dartdocVersion = packageVersion; - -class DartdocFileWriter implements FileWriter { - final String _outputDir; - @override - final ResourceProvider resourceProvider; - final Map _fileElementMap = {}; - @override - final Set writtenFiles = {}; - - DartdocFileWriter(this._outputDir, this.resourceProvider); - - @override - void writeBytes( - String filePath, - List content, { - bool allowOverwrite = false, - }) { - // Replace '/' separators with proper separators for the platform. - var outFile = path.joinAll(filePath.split('/')); - - if (!allowOverwrite) { - _warnAboutOverwrite(outFile, null); - } - _fileElementMap[outFile] = null; - - var file = _getFile(outFile); - file.writeAsBytesSync(content); - writtenFiles.add(outFile); - logProgress(outFile); - } - - @override - void write(String filePath, String content, {Warnable element}) { - // Replace '/' separators with proper separators for the platform. - var outFile = path.joinAll(filePath.split('/')); - - _warnAboutOverwrite(outFile, element); - _fileElementMap[outFile] = element; - - var file = _getFile(outFile); - file.writeAsStringSync(content); - writtenFiles.add(outFile); - logProgress(outFile); - } - - void _warnAboutOverwrite(String outFile, Warnable element) { - if (_fileElementMap.containsKey(outFile)) { - assert(element != null, - 'Attempted overwrite of $outFile without corresponding element'); - var originalElement = _fileElementMap[outFile]; - var referredFrom = originalElement != null ? [originalElement] : null; - element?.warn(PackageWarning.duplicateFile, - message: outFile, referredFrom: referredFrom); - } - } - - /// Returns the file at [outFile] relative to [_outputDir], creating the - /// parent directory if necessary. - File _getFile(String outFile) { - var file = resourceProvider - .getFile(resourceProvider.pathContext.join(_outputDir, outFile)); - var parent = file.parent2; - if (!parent.exists) { - parent.create(); - } - return file; - } -} - -/// Generates Dart documentation for all public Dart libraries in the given -/// directory. -class Dartdoc { - Generator _generator; - final PackageBuilder packageBuilder; - final DartdocOptionContext config; - final Set _writtenFiles = {}; - Folder _outputDir; - - // Fires when the self checks make progress. - final StreamController _onCheckProgress = - StreamController(sync: true); - - Dartdoc._(this.config, this._generator, this.packageBuilder) { - _outputDir = config.resourceProvider - .getFolder(config.resourceProvider.pathContext.absolute(config.output)) - ..create(); - } - - // TODO(srawlins): Remove when https://github.com/dart-lang/linter/issues/2706 - // is fixed. - // ignore: unnecessary_getters_setters - Generator get generator => _generator; - - @visibleForTesting - // TODO(srawlins): Remove when https://github.com/dart-lang/linter/issues/2706 - // is fixed. - // ignore: unnecessary_getters_setters - set generator(Generator newGenerator) => _generator = newGenerator; - - /// Asynchronous factory method that builds Dartdoc with an empty generator. - static Future withEmptyGenerator( - DartdocOptionContext config, - PackageBuilder packageBuilder, - ) async { - return Dartdoc._( - config, - await initEmptyGenerator(config), - packageBuilder, - ); - } - - /// Asynchronous factory method that builds Dartdoc with a generator - /// determined by the given context. - static Future fromContext( - DartdocGeneratorOptionContext context, - PackageBuilder packageBuilder, - ) async { - Generator generator; - switch (context.format) { - case 'html': - generator = await initHtmlGenerator(context); - break; - case 'md': - generator = await initMarkdownGenerator(context); - break; - default: - throw DartdocFailure('Unsupported output format: ${context.format}'); - } - return Dartdoc._( - context, - generator, - packageBuilder, - ); - } - - Stream get onCheckProgress => _onCheckProgress.stream; - - @visibleForTesting - Future generateDocsBase() async { - var stopwatch = Stopwatch()..start(); - var packageGraph = await packageBuilder.buildPackageGraph(); - var seconds = stopwatch.elapsedMilliseconds / 1000.0; - var libs = packageGraph.libraries.length; - logInfo("Initialized dartdoc with $libs librar${libs == 1 ? 'y' : 'ies'} " - 'in ${seconds.toStringAsFixed(1)} seconds'); - stopwatch.reset(); - - // Create the out directory. - if (!_outputDir.exists) _outputDir.create(); - - var writer = DartdocFileWriter(_outputDir.path, config.resourceProvider); - await generator.generate(packageGraph, writer); - - _writtenFiles.addAll(writer.writtenFiles); - if (config.validateLinks && _writtenFiles.isNotEmpty) { - _validateLinks(packageGraph, _outputDir.path); - } - - var warnings = packageGraph.packageWarningCounter.warningCount; - var errors = packageGraph.packageWarningCounter.errorCount; - if (warnings == 0 && errors == 0) { - logInfo('no issues found'); - } else { - logWarning("Found $warnings ${pluralize('warning', warnings)} " - "and $errors ${pluralize('error', errors)}."); - } - - seconds = stopwatch.elapsedMilliseconds / 1000.0; - libs = packageGraph.localPublicLibraries.length; - logInfo("Documented $libs public librar${libs == 1 ? 'y' : 'ies'} " - 'in ${seconds.toStringAsFixed(1)} seconds'); - - if (config.showStats) { - logInfo(markdownStats.buildReport()); - } - return DartdocResults(config.topLevelPackageMeta, packageGraph, _outputDir); - } - - /// Generate Dartdoc documentation. - /// - /// [DartdocResults] is returned if dartdoc succeeds. [DartdocFailure] is - /// thrown if dartdoc fails in an expected way, for example if there is an - /// analysis error in the code. - Future generateDocs() async { - DartdocResults dartdocResults; - try { - logInfo('Documenting ${config.topLevelPackageMeta}...'); - - dartdocResults = await generateDocsBase(); - if (dartdocResults.packageGraph.localPublicLibraries.isEmpty) { - logWarning('dartdoc could not find any libraries to document'); - } - - final errorCount = - dartdocResults.packageGraph.packageWarningCounter.errorCount; - if (errorCount > 0) { - throw DartdocFailure('encountered $errorCount errors'); - } - var outDirPath = config.resourceProvider.pathContext - .absolute(dartdocResults.outDir.path); - logInfo('Success! Docs generated into $outDirPath'); - return dartdocResults; - } finally { - dartdocResults?.packageGraph?.dispose(); - } - } - - /// Warn on file paths. - void _warn(PackageGraph packageGraph, PackageWarning kind, String warnOn, - String origin, - {String referredFrom}) { - // Ordinarily this would go in [Package.warn], but we don't actually know what - // ModelElement to warn on yet. - Warnable warnOnElement; - var referredFromElements = {}; - Set warnOnElements; - - // Make all paths relative to origin. - if (path.isWithin(origin, warnOn)) { - warnOn = path.relative(warnOn, from: origin); - } - if (referredFrom != null) { - if (path.isWithin(origin, referredFrom)) { - referredFrom = path.relative(referredFrom, from: origin); - } - // Source paths are always relative. - if (_hrefs[referredFrom] != null) { - referredFromElements.addAll(_hrefs[referredFrom]); - } - } - warnOnElements = _hrefs[warnOn]; - - if (referredFromElements.any((e) => e.isCanonical)) { - referredFromElements.removeWhere((e) => !e.isCanonical); - } - if (warnOnElements != null) { - warnOnElement = warnOnElements.firstWhere((e) => e.isCanonical, - orElse: () => warnOnElements.isEmpty ? null : warnOnElements.first); - } - - if (referredFromElements.isEmpty && referredFrom == 'index.html') { - referredFromElements.add(packageGraph.defaultPackage); - } - var message = warnOn; - if (referredFrom == 'index.json') message = '$warnOn (from index.json)'; - packageGraph.warnOnElement(warnOnElement, kind, - message: message, referredFrom: referredFromElements); - } - - void _doOrphanCheck( - PackageGraph packageGraph, String origin, Set visited) { - var normalOrigin = path.normalize(origin); - var staticAssets = path.joinAll([normalOrigin, 'static-assets', '']); - var indexJson = path.joinAll([normalOrigin, 'index.json']); - var foundIndexJson = false; - - void checkDirectory(Folder dir) { - for (var f in dir.getChildren()) { - if (f is Folder) { - checkDirectory(f); - continue; - } - var fullPath = path.normalize(f.path); - if (fullPath.startsWith(staticAssets)) { - continue; - } - if (path.equals(fullPath, indexJson)) { - foundIndexJson = true; - _onCheckProgress.add(fullPath); - continue; - } - if (visited.contains(fullPath)) continue; - var relativeFullPath = path.relative(fullPath, from: normalOrigin); - if (!_writtenFiles.contains(relativeFullPath)) { - // This isn't a file we wrote (this time); don't claim we did. - _warn( - packageGraph, PackageWarning.unknownFile, fullPath, normalOrigin); - } else { - // Error messages are orphaned by design and do not appear in the search - // index. - if (const {'__404error.html', 'categories.json'}.contains(fullPath)) { - _warn(packageGraph, PackageWarning.orphanedFile, fullPath, - normalOrigin); - } - } - _onCheckProgress.add(fullPath); - } - } - - checkDirectory(config.resourceProvider.getFolder(normalOrigin)); - - if (!foundIndexJson) { - _warn(packageGraph, PackageWarning.brokenLink, indexJson, normalOrigin); - _onCheckProgress.add(indexJson); - } - } - - // This is extracted to save memory during the check; be careful not to hang - // on to anything referencing the full file and doc tree. - Tuple2, String> _getStringLinksAndHref(String fullPath) { - var file = config.resourceProvider.getFile(fullPath); - if (!file.exists) { - return null; - } - // TODO(srawlins): It is possible that instantiating an HtmlParser using - // `lowercaseElementName: false` and `lowercaseAttrName: false` may save - // time or memory. - var doc = parse(file.readAsBytesSync()); - var base = doc.querySelector('base'); - String baseHref; - if (base != null) { - baseHref = base.attributes['href']; - } - var links = doc.querySelectorAll('a'); - var stringLinks = links - .map((link) => link.attributes['href']) - .where((href) => href != null && href != '') - .toList(); - - return Tuple2(stringLinks, baseHref); - } - - void _doSearchIndexCheck( - PackageGraph packageGraph, String origin, Set visited) { - var fullPath = path.joinAll([origin, 'index.json']); - var indexPath = path.joinAll([origin, 'index.html']); - var file = config.resourceProvider.getFile(fullPath); - if (!file.exists) { - return; - } - var decoder = JsonDecoder(); - List jsonData = decoder.convert(file.readAsStringSync()); - - var found = {}; - found.add(fullPath); - // The package index isn't supposed to be in the search, so suppress the - // warning. - found.add(indexPath); - for (Map entry in jsonData) { - if (entry.containsKey('href')) { - var entryPath = - path.joinAll([origin, ...path.posix.split(entry['href'])]); - if (!visited.contains(entryPath)) { - _warn(packageGraph, PackageWarning.brokenLink, entryPath, - path.normalize(origin), - referredFrom: fullPath); - } - found.add(entryPath); - } - } - // Missing from search index - var missingFromSearch = visited.difference(found); - for (var s in missingFromSearch) { - _warn(packageGraph, PackageWarning.missingFromSearchIndex, s, - path.normalize(origin), - referredFrom: fullPath); - } - } - - void _doCheck(PackageGraph packageGraph, String origin, Set visited, - String pathToCheck, - [String source, String fullPath]) { - fullPath ??= path.normalize(path.joinAll([origin, pathToCheck])); - - var stringLinksAndHref = _getStringLinksAndHref(fullPath); - if (stringLinksAndHref == null) { - _warn(packageGraph, PackageWarning.brokenLink, pathToCheck, - path.normalize(origin), - referredFrom: source); - _onCheckProgress.add(pathToCheck); - // Remove so that we properly count that the file doesn't exist for - // the orphan check. - visited.remove(fullPath); - return; - } - visited.add(fullPath); - var stringLinks = stringLinksAndHref.item1; - var baseHref = stringLinksAndHref.item2; - - // Prevent extremely large stacks by storing the paths we are using - // here instead -- occasionally, very large jobs have overflowed - // the stack without this. - // (newPathToCheck, newFullPath) - var toVisit = >{}; - - final ignoreHyperlinks = RegExp(r'^(https:|http:|mailto:|ftp:)'); - for (final href in stringLinks) { - if (!href.startsWith(ignoreHyperlinks)) { - final uri = Uri.tryParse(href); - - if (uri == null || !uri.hasAuthority && !uri.hasFragment) { - String full; - if (baseHref != null) { - full = '${path.dirname(pathToCheck)}/$baseHref/$href'; - } else { - full = '${path.dirname(pathToCheck)}/$href'; - } - - final newPathToCheck = path.normalize(full); - final newFullPath = - path.normalize(path.joinAll([origin, newPathToCheck])); - if (!visited.contains(newFullPath)) { - toVisit.add(Tuple2(newPathToCheck, newFullPath)); - visited.add(newFullPath); - } - } - } - } - for (var visitPaths in toVisit) { - _doCheck(packageGraph, origin, visited, visitPaths.item1, pathToCheck, - visitPaths.item2); - } - _onCheckProgress.add(pathToCheck); - } - - Map> _hrefs; - - /// Don't call this method more than once, and only after you've - /// generated all docs for the Package. - void _validateLinks(PackageGraph packageGraph, String origin) { - assert(_hrefs == null); - _hrefs = packageGraph.allHrefs; - - final visited = {}; - logInfo('Validating docs...'); - _doCheck(packageGraph, origin, visited, 'index.html'); - _doOrphanCheck(packageGraph, origin, visited); - _doSearchIndexCheck(packageGraph, origin, visited); - } - - /// Runs [generateDocs] function and properly handles the errors. - /// - /// Passing in a [postProcessCallback] to do additional processing after - /// the documentation is generated. - void executeGuarded([ - Future Function(DartdocOptionContext) postProcessCallback, - ]) { - onCheckProgress.listen(logProgress); - // This function should *never* await `runZonedGuarded` because the errors - // thrown in generateDocs are uncaught. We want this because uncaught errors - // cause IDE debugger to automatically stop at the exception. - // - // If you await the zone, the code that comes after the await is not - // executed if the zone dies due to uncaught error. To avoid this confusion, - // never await `runZonedGuarded` and never change the return value of - // [executeGuarded]. - runZonedGuarded( - () async { - await generateDocs(); - await postProcessCallback?.call(config); - }, - (e, chain) { - if (e is DartdocFailure) { - stderr.writeln('\n$_dartdocFailedMessage: $e.'); - exitCode = 1; - } else { - stderr.writeln('\n$_dartdocFailedMessage: $e\n$chain'); - exitCode = 255; - } - }, - zoneSpecification: ZoneSpecification( - print: (_, __, ___, String line) => logPrint(line), - ), - ); - } -} - -/// The results of a [Dartdoc.generateDocs] call. -class DartdocResults { - final PackageMeta packageMeta; - final PackageGraph packageGraph; - final Folder outDir; - - DartdocResults(this.packageMeta, this.packageGraph, this.outDir); -} - -String get _dartdocFailedMessage => - 'dartdoc $packageVersion (${Platform.script.path}) failed'; diff --git a/lib/src/dartdoc.dart b/lib/src/dartdoc.dart new file mode 100644 index 0000000000..d15bc05b09 --- /dev/null +++ b/lib/src/dartdoc.dart @@ -0,0 +1,515 @@ +// Copyright (c) 2014, 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. + +library src.dartdoc; + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io' show Platform, exitCode, stderr; + +import 'package:analyzer/file_system/file_system.dart'; +import 'package:dartdoc/options.dart'; +import 'package:dartdoc/src/dartdoc_options.dart'; +import 'package:dartdoc/src/failure.dart'; +import 'package:dartdoc/src/generator/empty_generator.dart'; +import 'package:dartdoc/src/generator/generator.dart'; +import 'package:dartdoc/src/generator/html_generator.dart'; +import 'package:dartdoc/src/generator/markdown_generator.dart'; +import 'package:dartdoc/src/logging.dart'; +import 'package:dartdoc/src/matching_link_result.dart'; +import 'package:dartdoc/src/model/model.dart'; +import 'package:dartdoc/src/package_meta.dart'; +import 'package:dartdoc/src/tuple.dart'; +import 'package:dartdoc/src/utils.dart'; +import 'package:dartdoc/src/version.dart'; +import 'package:dartdoc/src/warnings.dart'; +import 'package:html/parser.dart' show parse; +import 'package:meta/meta.dart'; +import 'package:path/path.dart' as path; + +const String programName = 'dartdoc'; +// Update when pubspec version changes by running `pub run build_runner build` +const String dartdocVersion = packageVersion; + +class DartdocFileWriter implements FileWriter { + final String _outputDir; + @override + final ResourceProvider resourceProvider; + final Map _fileElementMap = {}; + @override + final Set writtenFiles = {}; + + DartdocFileWriter(this._outputDir, this.resourceProvider); + + @override + void writeBytes( + String filePath, + List content, { + bool allowOverwrite = false, + }) { + // Replace '/' separators with proper separators for the platform. + var outFile = path.joinAll(filePath.split('/')); + + if (!allowOverwrite) { + _warnAboutOverwrite(outFile, null); + } + _fileElementMap[outFile] = null; + + var file = _getFile(outFile); + file.writeAsBytesSync(content); + writtenFiles.add(outFile); + logProgress(outFile); + } + + @override + void write(String filePath, String content, {Warnable? element}) { + // Replace '/' separators with proper separators for the platform. + var outFile = path.joinAll(filePath.split('/')); + + _warnAboutOverwrite(outFile, element); + _fileElementMap[outFile] = element; + + var file = _getFile(outFile); + file.writeAsStringSync(content); + writtenFiles.add(outFile); + logProgress(outFile); + } + + void _warnAboutOverwrite(String outFile, Warnable? element) { + if (_fileElementMap.containsKey(outFile)) { + assert(element != null, + 'Attempted overwrite of $outFile without corresponding element'); + var originalElement = _fileElementMap[outFile]; + var referredFrom = []; + if (originalElement != null) referredFrom.add(originalElement); + element?.warn(PackageWarning.duplicateFile, + message: outFile, referredFrom: referredFrom); + } + } + + /// Returns the file at [outFile] relative to [_outputDir], creating the + /// parent directory if necessary. + File _getFile(String outFile) { + var file = resourceProvider + .getFile(resourceProvider.pathContext.join(_outputDir, outFile)); + var parent = file.parent2; + if (!parent.exists) { + parent.create(); + } + return file; + } +} + +/// Generates Dart documentation for all public Dart libraries in the given +/// directory. +class Dartdoc { + Generator _generator; + final PackageBuilder packageBuilder; + final DartdocOptionContext config; + final Set _writtenFiles = {}; + late final Folder _outputDir = config.resourceProvider + .getFolder(config.resourceProvider.pathContext.absolute(config.output)) + ..create(); + + // Fires when the self checks make progress. + final StreamController _onCheckProgress = + StreamController(sync: true); + + Dartdoc._(this.config, this._generator, this.packageBuilder); + + // TODO(srawlins): Remove when https://github.com/dart-lang/linter/issues/2706 + // is fixed. + // ignore: unnecessary_getters_setters + Generator get generator => _generator; + + @visibleForTesting + // TODO(srawlins): Remove when https://github.com/dart-lang/linter/issues/2706 + // is fixed. + // ignore: unnecessary_getters_setters + set generator(Generator newGenerator) => _generator = newGenerator; + + /// Asynchronous factory method that builds Dartdoc with an empty generator. + static Future withEmptyGenerator( + DartdocOptionContext config, + PackageBuilder packageBuilder, + ) async { + return Dartdoc._( + config, + await initEmptyGenerator(config), + packageBuilder, + ); + } + + /// Asynchronous factory method that builds Dartdoc with a generator + /// determined by the given context. + static Future fromContext( + DartdocGeneratorOptionContext context, + PackageBuilder packageBuilder, + ) async { + Generator generator; + switch (context.format) { + case 'html': + generator = await initHtmlGenerator(context); + break; + case 'md': + generator = await initMarkdownGenerator(context); + break; + default: + throw DartdocFailure('Unsupported output format: ${context.format}'); + } + return Dartdoc._( + context, + generator, + packageBuilder, + ); + } + + Stream get onCheckProgress => _onCheckProgress.stream; + + @visibleForTesting + Future generateDocsBase() async { + var stopwatch = Stopwatch()..start(); + var packageGraph = await packageBuilder.buildPackageGraph(); + var seconds = stopwatch.elapsedMilliseconds / 1000.0; + var libs = packageGraph.libraries.length; + logInfo("Initialized dartdoc with $libs librar${libs == 1 ? 'y' : 'ies'} " + 'in ${seconds.toStringAsFixed(1)} seconds'); + stopwatch.reset(); + + // Create the out directory. + if (!_outputDir.exists) _outputDir.create(); + + var writer = DartdocFileWriter(_outputDir.path, config.resourceProvider); + await generator.generate(packageGraph, writer); + + _writtenFiles.addAll(writer.writtenFiles); + if (config.validateLinks && _writtenFiles.isNotEmpty) { + _validateLinks(packageGraph, _outputDir.path); + } + + var warnings = packageGraph.packageWarningCounter.warningCount; + var errors = packageGraph.packageWarningCounter.errorCount; + if (warnings == 0 && errors == 0) { + logInfo('no issues found'); + } else { + logWarning("Found $warnings ${pluralize('warning', warnings)} " + "and $errors ${pluralize('error', errors)}."); + } + + seconds = stopwatch.elapsedMilliseconds / 1000.0; + libs = packageGraph.localPublicLibraries.length; + logInfo("Documented $libs public librar${libs == 1 ? 'y' : 'ies'} " + 'in ${seconds.toStringAsFixed(1)} seconds'); + + if (config.showStats) { + logInfo(markdownStats.buildReport()); + } + return DartdocResults(config.topLevelPackageMeta, packageGraph, _outputDir); + } + + /// Generate Dartdoc documentation. + /// + /// [DartdocResults] is returned if dartdoc succeeds. [DartdocFailure] is + /// thrown if dartdoc fails in an expected way, for example if there is an + /// analysis error in the code. + Future generateDocs() async { + DartdocResults? dartdocResults; + try { + logInfo('Documenting ${config.topLevelPackageMeta}...'); + + dartdocResults = await generateDocsBase(); + if (dartdocResults.packageGraph.localPublicLibraries.isEmpty) { + logWarning('dartdoc could not find any libraries to document'); + } + + final errorCount = + dartdocResults.packageGraph.packageWarningCounter.errorCount; + if (errorCount > 0) { + throw DartdocFailure('encountered $errorCount errors'); + } + var outDirPath = config.resourceProvider.pathContext + .absolute(dartdocResults.outDir.path); + logInfo('Success! Docs generated into $outDirPath'); + return dartdocResults; + } finally { + dartdocResults?.packageGraph.dispose(); + } + } + + /// Warn on file paths. + void _warn(PackageGraph packageGraph, PackageWarning kind, String warnOn, + String origin, + {String? referredFrom}) { + // Ordinarily this would go in [Package.warn], but we don't actually know what + // ModelElement to warn on yet. + Warnable? warnOnElement; + var referredFromElements = {}; + Set? warnOnElements; + + // Make all paths relative to origin. + if (path.isWithin(origin, warnOn)) { + warnOn = path.relative(warnOn, from: origin); + } + if (referredFrom != null) { + if (path.isWithin(origin, referredFrom)) { + referredFrom = path.relative(referredFrom, from: origin); + } + var hrefReferredFrom = _hrefs[referredFrom]; + // Source paths are always relative. + if (hrefReferredFrom != null) { + referredFromElements.addAll(hrefReferredFrom); + } + } + warnOnElements = _hrefs[warnOn]; + + if (referredFromElements.any((e) => e.isCanonical)) { + referredFromElements.removeWhere((e) => !e.isCanonical); + } + if (warnOnElements != null) { + for (var e in warnOnElements) { + if (e.isCanonical) { + warnOnElement = e; + break; + } + } + } + + if (referredFromElements.isEmpty && referredFrom == 'index.html') { + referredFromElements.add(packageGraph.defaultPackage); + } + var message = warnOn; + if (referredFrom == 'index.json') message = '$warnOn (from index.json)'; + packageGraph.warnOnElement(warnOnElement, kind, + message: message, referredFrom: referredFromElements); + } + + void _doOrphanCheck( + PackageGraph packageGraph, String origin, Set visited) { + var normalOrigin = path.normalize(origin); + var staticAssets = path.joinAll([normalOrigin, 'static-assets', '']); + var indexJson = path.joinAll([normalOrigin, 'index.json']); + var foundIndexJson = false; + + void checkDirectory(Folder dir) { + for (var f in dir.getChildren()) { + if (f is Folder) { + checkDirectory(f); + continue; + } + var fullPath = path.normalize(f.path); + if (fullPath.startsWith(staticAssets)) { + continue; + } + if (path.equals(fullPath, indexJson)) { + foundIndexJson = true; + _onCheckProgress.add(fullPath); + continue; + } + if (visited.contains(fullPath)) continue; + var relativeFullPath = path.relative(fullPath, from: normalOrigin); + if (!_writtenFiles.contains(relativeFullPath)) { + // This isn't a file we wrote (this time); don't claim we did. + _warn( + packageGraph, PackageWarning.unknownFile, fullPath, normalOrigin); + } else { + // Error messages are orphaned by design and do not appear in the search + // index. + if (const {'__404error.html', 'categories.json'}.contains(fullPath)) { + _warn(packageGraph, PackageWarning.orphanedFile, fullPath, + normalOrigin); + } + } + _onCheckProgress.add(fullPath); + } + } + + checkDirectory(config.resourceProvider.getFolder(normalOrigin)); + + if (!foundIndexJson) { + _warn(packageGraph, PackageWarning.brokenLink, indexJson, normalOrigin); + _onCheckProgress.add(indexJson); + } + } + + // This is extracted to save memory during the check; be careful not to hang + // on to anything referencing the full file and doc tree. + Tuple2, String?>? _getStringLinksAndHref(String fullPath) { + var file = config.resourceProvider.getFile(fullPath); + if (!file.exists) { + return null; + } + // TODO(srawlins): It is possible that instantiating an HtmlParser using + // `lowercaseElementName: false` and `lowercaseAttrName: false` may save + // time or memory. + var doc = parse(file.readAsBytesSync()); + var base = doc.querySelector('base'); + String? baseHref; + if (base != null) { + baseHref = base.attributes['href']; + } + var links = doc.querySelectorAll('a'); + var stringLinks = links + .map((link) => link.attributes['href']) + .whereType() + .where((href) => href != '') + .toList(); + + return Tuple2(stringLinks, baseHref); + } + + void _doSearchIndexCheck( + PackageGraph packageGraph, String origin, Set visited) { + var fullPath = path.joinAll([origin, 'index.json']); + var indexPath = path.joinAll([origin, 'index.html']); + var file = config.resourceProvider.getFile(fullPath); + if (!file.exists) { + return; + } + var decoder = JsonDecoder(); + List jsonData = decoder.convert(file.readAsStringSync()); + + var found = {}; + found.add(fullPath); + // The package index isn't supposed to be in the search, so suppress the + // warning. + found.add(indexPath); + for (Map entry in jsonData) { + if (entry.containsKey('href')) { + var entryPath = + path.joinAll([origin, ...path.posix.split(entry['href'])]); + if (!visited.contains(entryPath)) { + _warn(packageGraph, PackageWarning.brokenLink, entryPath, + path.normalize(origin), + referredFrom: fullPath); + } + found.add(entryPath); + } + } + // Missing from search index + var missingFromSearch = visited.difference(found); + for (var s in missingFromSearch) { + _warn(packageGraph, PackageWarning.missingFromSearchIndex, s, + path.normalize(origin), + referredFrom: fullPath); + } + } + + void _doCheck(PackageGraph packageGraph, String origin, Set visited, + String pathToCheck, + [String? source, String? fullPath]) { + fullPath ??= path.normalize(path.joinAll([origin, pathToCheck])); + + var stringLinksAndHref = _getStringLinksAndHref(fullPath); + if (stringLinksAndHref == null) { + _warn(packageGraph, PackageWarning.brokenLink, pathToCheck, + path.normalize(origin), + referredFrom: source); + _onCheckProgress.add(pathToCheck); + // Remove so that we properly count that the file doesn't exist for + // the orphan check. + visited.remove(fullPath); + return; + } + visited.add(fullPath); + var stringLinks = stringLinksAndHref.item1; + var baseHref = stringLinksAndHref.item2; + + // Prevent extremely large stacks by storing the paths we are using + // here instead -- occasionally, very large jobs have overflowed + // the stack without this. + // (newPathToCheck, newFullPath) + var toVisit = >{}; + + final ignoreHyperlinks = RegExp(r'^(https:|http:|mailto:|ftp:)'); + for (final href in stringLinks) { + if (!href.startsWith(ignoreHyperlinks)) { + final uri = Uri.tryParse(href); + + if (uri == null || !uri.hasAuthority && !uri.hasFragment) { + String full; + if (baseHref != null) { + full = '${path.dirname(pathToCheck)}/$baseHref/$href'; + } else { + full = '${path.dirname(pathToCheck)}/$href'; + } + + final newPathToCheck = path.normalize(full); + final newFullPath = + path.normalize(path.joinAll([origin, newPathToCheck])); + if (!visited.contains(newFullPath)) { + toVisit.add(Tuple2(newPathToCheck, newFullPath)); + visited.add(newFullPath); + } + } + } + } + for (var visitPaths in toVisit) { + _doCheck(packageGraph, origin, visited, visitPaths.item1, pathToCheck, + visitPaths.item2); + } + _onCheckProgress.add(pathToCheck); + } + + late final Map> _hrefs; + + /// Don't call this method more than once, and only after you've + /// generated all docs for the Package. + void _validateLinks(PackageGraph packageGraph, String origin) { + _hrefs = packageGraph.allHrefs; + + final visited = {}; + logInfo('Validating docs...'); + _doCheck(packageGraph, origin, visited, 'index.html'); + _doOrphanCheck(packageGraph, origin, visited); + _doSearchIndexCheck(packageGraph, origin, visited); + } + + /// Runs [generateDocs] function and properly handles the errors. + /// + /// Passing in a [postProcessCallback] to do additional processing after + /// the documentation is generated. + void executeGuarded([ + Future Function(DartdocOptionContext)? postProcessCallback, + ]) { + onCheckProgress.listen(logProgress); + // This function should *never* await `runZonedGuarded` because the errors + // thrown in generateDocs are uncaught. We want this because uncaught errors + // cause IDE debugger to automatically stop at the exception. + // + // If you await the zone, the code that comes after the await is not + // executed if the zone dies due to uncaught error. To avoid this confusion, + // never await `runZonedGuarded` and never change the return value of + // [executeGuarded]. + runZonedGuarded( + () async { + await generateDocs(); + await postProcessCallback?.call(config); + }, + (e, chain) { + if (e is DartdocFailure) { + stderr.writeln('\n$_dartdocFailedMessage: $e.'); + exitCode = 1; + } else { + stderr.writeln('\n$_dartdocFailedMessage: $e\n$chain'); + exitCode = 255; + } + }, + zoneSpecification: ZoneSpecification( + print: (_, __, ___, String line) => logPrint(line), + ), + ); + } +} + +/// The results of a [Dartdoc.generateDocs] call. +class DartdocResults { + final PackageMeta packageMeta; + final PackageGraph packageGraph; + final Folder outDir; + + DartdocResults(this.packageMeta, this.packageGraph, this.outDir); +} + +String get _dartdocFailedMessage => + 'dartdoc $packageVersion (${Platform.script.path}) failed'; diff --git a/lib/src/model/package_graph.dart b/lib/src/model/package_graph.dart index be9690206c..cfc76e9d0c 100644 --- a/lib/src/model/package_graph.dart +++ b/lib/src/model/package_graph.dart @@ -753,7 +753,8 @@ class PackageGraph with CommentReferable, Nameable, ModelBuilder { /// This doesn't know anything about [PackageGraph.inheritThrough] and probably /// shouldn't, so using it with [Inheritable]s without special casing is /// not advised. - ModelElement findCanonicalModelElementFor(Element e, + // FIXME(nnbd): remove null check ignore in model_utils after migration + ModelElement /*?*/ findCanonicalModelElementFor(Element e, {Container preferredClass}) { assert(allLibrariesAdded); var lib = findCanonicalLibraryFor(e); diff --git a/lib/src/model_utils.dart b/lib/src/model_utils.dart index 670fd9d0f2..6509d6cb58 100644 --- a/lib/src/model_utils.dart +++ b/lib/src/model_utils.dart @@ -2,8 +2,6 @@ // 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. -// @dart=2.9 - library dartdoc.model_utils; import 'dart:convert'; @@ -28,11 +26,11 @@ final Map _fileContents = {}; /// On windows, globs are assumed to use absolute Windows paths with drive /// letters in combination with globs, e.g. `C:\foo\bar\*.txt`. `fullName` /// also is assumed to have a drive letter. -bool matchGlobs(List globs, String fullName, {bool isWindows}) { - isWindows ??= Platform.isWindows; +bool matchGlobs(List globs, String fullName, {bool? isWindows}) { + var windows = isWindows ?? Platform.isWindows; var filteredGlobs = []; - if (isWindows) { + if (windows) { // TODO(jcollins-g): port this special casing to the glob package. var fullNameDriveLetter = _driveLetterMatcher.stringMatch(fullName); if (fullNameDriveLetter == null) { @@ -55,20 +53,18 @@ bool matchGlobs(List globs, String fullName, {bool isWindows}) { } return filteredGlobs.any((g) => - Glob(g, context: isWindows ? path.windows : path.posix) - .matches(fullName)); + Glob(g, context: windows ? path.windows : path.posix).matches(fullName)); } /// Returns the [AstNode] for a given [Element]. /// /// Uses a precomputed map of [element.source.fullName] to [CompilationUnit] /// to avoid linear traversal in [ResolvedLibraryElementImpl.getElementDeclaration]. -AstNode getAstNode( +AstNode? getAstNode( Element element, Map compilationUnitMap) { - if (element?.source?.fullName != null && - !element.isSynthetic && - element.nameOffset != -1) { - var unit = compilationUnitMap[element.source.fullName]; + var fullName = element.source?.fullName; + if (fullName != null && !element.isSynthetic && element.nameOffset != -1) { + var unit = compilationUnitMap[fullName]; if (unit != null) { var locator = NodeLocator2(element.nameOffset); return (locator.searchWithin(unit)?.parent); @@ -100,6 +96,8 @@ Iterable findCanonicalFor( return containers.map((c) => c.packageGraph.findCanonicalModelElementFor(c.element) as InheritingContainer ?? + // FIXME(nnbd) : remove ignore after package_graph is migrated + // ignore: dead_null_aware_expression c); } @@ -109,9 +107,9 @@ Iterable findCanonicalFor( /// allowed in some environments, so avoid using this. // TODO(jcollins-g): consider deprecating this and the `--include-source` // feature that uses it now that source code linking is possible. -String getFileContentsFor(Element e, ResourceProvider resourceProvider) { - var location = e.source.fullName; - if (!_fileContents.containsKey(location)) { +String? getFileContentsFor(Element e, ResourceProvider resourceProvider) { + var location = e.source?.fullName; + if (location != null && !_fileContents.containsKey(location)) { var contents = resourceProvider.getFile(location).readAsStringSync(); _fileContents.putIfAbsent(location, () => contents); } @@ -120,15 +118,17 @@ String getFileContentsFor(Element e, ResourceProvider resourceProvider) { final RegExp slashes = RegExp(r'[\/]'); bool hasPrivateName(Element e) { - if (e.name == null) return false; + var elementName = e.name; + if (elementName == null) return false; - if (e.name.startsWith('_')) { + if (elementName.startsWith('_')) { return true; } // GenericFunctionTypeElements have the name we care about in the enclosing // element. if (e is GenericFunctionTypeElement) { - if (e.enclosingElement.name.startsWith('_')) { + var enclosingElementName = e.enclosingElement?.name; + if (enclosingElementName != null && enclosingElementName.startsWith('_')) { return true; } } @@ -139,11 +139,14 @@ bool hasPrivateName(Element e) { return true; } if (e is LibraryElement) { - var locationParts = e.location.components[0].split(slashes); - // TODO(jcollins-g): Implement real cross package detection - if (locationParts.length >= 2 && - locationParts[0].startsWith('package:') && - locationParts[1] == 'src') return true; + var elementLocation = e.location; + if (elementLocation != null) { + var locationParts = elementLocation.components[0].split(slashes); + // TODO(jcollins-g): Implement real cross package detection + if (locationParts.length >= 2 && + locationParts[0].startsWith('package:') && + locationParts[1] == 'src') return true; + } } return false; } diff --git a/lib/src/special_elements.dart b/lib/src/special_elements.dart index fd8d98191e..49907b57c2 100644 --- a/lib/src/special_elements.dart +++ b/lib/src/special_elements.dart @@ -2,8 +2,6 @@ // 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. -// @dart=2.9 - /// Handling for special elements within Dart. When identified these /// may alter the interpretation and documentation generated for other /// [ModelElement]s. @@ -60,7 +58,7 @@ class _SpecialClassDefinition { /// Get the filename for the Dart Library where this [ModelElement] /// is declared, or `null` if its URI does not denote a library in /// the specified SDK. - String /*?*/ getSpecialFilename(DartSdk sdk) => + String? getSpecialFilename(DartSdk sdk) => sdk.mapDartUri(specialFileUri)?.fullName; bool matchesClass(Class modelClass) { @@ -90,7 +88,7 @@ const Map _specialClassDefinitions = { /// classes. Set specialLibraryFiles(DartSdk sdk) => _specialClassDefinitions.values .map((_SpecialClassDefinition d) => d.getSpecialFilename(sdk)) - .where((String s) => s != null) + .whereType() .toSet(); /// Class for managing special [Class] objects inside Dartdoc. @@ -102,7 +100,7 @@ class SpecialClasses { /// Add a class object that could be special. void addSpecial(Class aClass) { if (_specialClassDefinitions.containsKey(aClass.name)) { - var d = _specialClassDefinitions[aClass.name]; + var d = _specialClassDefinitions[aClass.name]!; if (d.matchesClass(aClass)) { assert(!_specialClass.containsKey(d.specialClass) || _specialClass[d.specialClass] == aClass); @@ -119,5 +117,5 @@ class SpecialClasses { } } - Class operator [](SpecialClass specialClass) => _specialClass[specialClass]; + Class? operator [](SpecialClass specialClass) => _specialClass[specialClass]; } From e58770bb9c10bc5ac7bb8279051efed3d9a038c3 Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Tue, 12 Oct 2021 15:55:40 -0700 Subject: [PATCH 2/6] rebuild --- .../templates.runtime_renderers.dart | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/lib/src/generator/templates.runtime_renderers.dart b/lib/src/generator/templates.runtime_renderers.dart index 16816e4561..6dbb9a312e 100644 --- a/lib/src/generator/templates.runtime_renderers.dart +++ b/lib/src/generator/templates.runtime_renderers.dart @@ -1963,12 +1963,13 @@ class _Renderer_CommentReferable extends RendererBase { List remainingNames) => self.renderSimpleVariable( c, remainingNames, 'Iterable'), - renderIterable: (CT_ c, RendererBase r, + isNullValue: (CT_ c) => + c.referenceGrandparentOverrides == null, + renderValue: (CT_ c, RendererBase r, List ast, StringSink sink) { - return c.referenceGrandparentOverrides.map((e) => - renderSimple(e, ast, r.template, sink, - parent: r, - getters: _invisibleGetters['CommentReferable'])); + renderSimple( + c.referenceGrandparentOverrides, ast, r.template, sink, + parent: r, getters: _invisibleGetters['Iterable']); }, ), 'referenceName': Property( @@ -10761,18 +10762,6 @@ class _Renderer_Object extends RendererBase { parent: r, getters: _invisibleGetters['int']); }, ), - 'runtimeType': Property( - getValue: (CT_ c) => c.runtimeType, - renderVariable: (CT_ c, Property self, - List remainingNames) => - self.renderSimpleVariable(c, remainingNames, 'Type'), - isNullValue: (CT_ c) => c.runtimeType == null, - renderValue: (CT_ c, RendererBase r, - List ast, StringSink sink) { - renderSimple(c.runtimeType, ast, r.template, sink, - parent: r, getters: _invisibleGetters['Type']); - }, - ), }); _Renderer_Object(Object context, RendererBase parent, @@ -15598,6 +15587,17 @@ const _invisibleGetters = { 'overriddenDepth' }, 'InheritanceManager3': {'hashCode', 'runtimeType'}, + 'Iterable': { + 'hashCode', + 'runtimeType', + 'iterator', + 'length', + 'isEmpty', + 'isNotEmpty', + 'first', + 'last', + 'single' + }, 'LibraryElement': { 'hashCode', 'runtimeType', @@ -15888,7 +15888,6 @@ const _invisibleGetters = { 'customFooterContent', 'customInnerFooterText' }, - 'Type': {'hashCode', 'runtimeType'}, 'TypeAliasElement': { 'hashCode', 'runtimeType', From 42b408488ca7611e09cedd6517a5d8bb9f658be1 Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Wed, 13 Oct 2021 11:05:38 -0700 Subject: [PATCH 3/6] migrate more things --- tool/builder.dart | 2 - tool/doc_packages.dart | 28 +++++------ tool/subprocess_launcher.dart | 91 ++++++++++++++++------------------- 3 files changed, 54 insertions(+), 67 deletions(-) diff --git a/tool/builder.dart b/tool/builder.dart index 31e7804ee5..c9d1aad05a 100644 --- a/tool/builder.dart +++ b/tool/builder.dart @@ -2,8 +2,6 @@ // 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. -// @dart=2.9 - import 'dart:async'; import 'package:build/build.dart'; diff --git a/tool/doc_packages.dart b/tool/doc_packages.dart index b8ea616150..244798f4a1 100644 --- a/tool/doc_packages.dart +++ b/tool/doc_packages.dart @@ -2,8 +2,6 @@ // 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. -// @dart=2.9 - /// A CLI tool to generate documentation for packages from pub.dartlang.org. library dartdoc.doc_packages; @@ -86,7 +84,7 @@ void performGenerate(int page) { _packageUrls(page).then((List packages) { return _getPackageInfos(packages).then((List infos) { - return Future.forEach(infos, (info) { + return Future.forEach(infos, (PackageInfo info) { return _printGenerationResult(info, _generateFor(info)); }); }); @@ -147,7 +145,7 @@ Future> _getPackageInfos(List packageUrls) { return Future.wait(futures); } -StringBuffer _logBuffer; +StringBuffer? _logBuffer; /// Generate the docs for the given package into _rootDir. Return whether /// generation was performed or was skipped (due to an older package). @@ -195,7 +193,7 @@ Future _generateFor(PackageInfo package) async { } Future _exec(String command, List args, - {String cwd, + {String? cwd, bool quiet = false, Duration timeout = const Duration(seconds: 60)}) { return Process.start(command, args, workingDirectory: cwd) @@ -209,20 +207,16 @@ Future _exec(String command, List args, if (code != 0) throw code; }); - if (timeout != null) { - return f.timeout(timeout, onTimeout: () { - _log('Timing out operation $command.'); - process.kill(); - throw 'timeout on $command'; - }); - } else { - return f; - } + return f.timeout(timeout, onTimeout: () { + _log('Timing out operation $command.'); + process.kill(); + throw 'timeout on $command'; + }); }); } bool _isOldSdkConstraint(Map pubspecInfo) { - var environment = pubspecInfo['environment'] as Map; + var environment = pubspecInfo['environment'] as Map?; if (environment != null) { var sdk = environment['sdk']; if (sdk != null) { @@ -243,8 +237,10 @@ bool _isOldSdkConstraint(Map pubspecInfo) { return false; } +/// Log entries will be dropped if [_logBuffer] has not been initialized. void _log(String str) { - _logBuffer.write(str); + assert(_logBuffer != null); + _logBuffer?.write(str); } class PackageInfo { diff --git a/tool/subprocess_launcher.dart b/tool/subprocess_launcher.dart index 679427ad70..f8770f0844 100644 --- a/tool/subprocess_launcher.dart +++ b/tool/subprocess_launcher.dart @@ -2,8 +2,6 @@ // 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. -// @dart=2.9 - import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -14,11 +12,11 @@ import 'package:path/path.dart' as p; /// Keeps track of coverage data automatically for any processes run by this /// [CoverageSubprocessLauncher]. Requires that these be dart processes. class CoverageSubprocessLauncher extends SubprocessLauncher { - CoverageSubprocessLauncher(String context, [Map environment]) - : super(context, environment) { - environment ??= {}; - environment['DARTDOC_COVERAGE_DATA'] = tempDir.path; - } + CoverageSubprocessLauncher(String context, [Map? environment]) + : super( + context, + (environment ?? {}) + ..addAll({'DARTDOC_COVERAGE_DATA': tempDir.path})); static int nextRun = 0; @@ -31,24 +29,20 @@ class CoverageSubprocessLauncher extends SubprocessLauncher { // is enabled. static List>>> coverageResults = []; - static Directory _tempDir; - static Directory get tempDir { - if (_tempDir == null) { - if (Platform.environment['DARTDOC_COVERAGE_DATA'] != null) { - _tempDir = Directory(Platform.environment['DARTDOC_COVERAGE_DATA']); - } else { - _tempDir = Directory.systemTemp.createTempSync('dartdoc_coverage_data'); - } + static Directory tempDir = () { + var coverageData = Platform.environment['DARTDOC_COVERAGE_DATA']; + if (coverageData != null) { + return Directory(coverageData); } - return _tempDir; - } + return Directory.systemTemp.createTempSync('dartdoc_coverage_data'); + }(); static String buildNextCoverageFilename() => p.join(tempDir.path, 'dart-cov-$pid-${nextRun++}.json'); /// Call once all coverage runs have been generated by calling runStreamed /// on all [CoverageSubprocessLaunchers]. - static Future generateCoverageToFile( + static Future generateCoverageToFile( File outputFile, ResourceProvider resourceProvider) async { if (!coverageEnabled) return Future.value(null); var currentCoverageResults = coverageResults; @@ -77,10 +71,10 @@ class CoverageSubprocessLauncher extends SubprocessLauncher { @override Future>> runStreamed( String executable, List arguments, - {String workingDirectory, - Map environment, + {String? workingDirectory, + Map? environment, bool includeParentEnvironment = true, - void Function(String) perLine}) async { + void Function(String)? perLine}) async { environment ??= {}; assert( executable == Platform.executable || @@ -91,16 +85,17 @@ class CoverageSubprocessLauncher extends SubprocessLauncher { void parsePortAsString(String line) { if (!portAsString.isCompleted && coverageEnabled) { var m = _observatoryPortRegexp.matchAsPrefix(line); - if (m?.group(1) != null) portAsString.complete(m.group(1)); + if (m != null) { + if (m.group(1) != null) portAsString.complete(m.group(1)); + } } else { if (perLine != null) perLine(line); } } - Completer>> coverageResult; + Completer>> coverageResult = Completer(); if (coverageEnabled) { - coverageResult = Completer(); // This must be added before awaiting in this method. coverageResults.add(coverageResult.future); arguments = [ @@ -139,29 +134,27 @@ class CoverageSubprocessLauncher extends SubprocessLauncher { class SubprocessLauncher { final String context; - final Map environmentDefaults; + final Map environmentDefaults = {}; String get prefix => context.isNotEmpty ? '$context: ' : ''; // from flutter:dev/tools/dartdoc.dart, modified static Future _printStream(Stream> stream, Stdout output, - {String prefix = '', Iterable Function(String line) filter}) { - assert(prefix != null); - filter ??= (line) => [line]; + {String prefix = '', + required Iterable Function(String line) filter}) { return stream .transform(utf8.decoder) .transform(const LineSplitter()) .expand(filter) .listen((String line) { - if (line != null) { - output.write('$prefix$line'.trim()); - output.write('\n'); - } + output.write('$prefix$line'.trim()); + output.write('\n'); }).asFuture(); } - SubprocessLauncher(this.context, [Map environment]) - : environmentDefaults = environment ?? {}; + SubprocessLauncher(this.context, [Map? environment]) { + environmentDefaults.addAll(environment ?? {}); + } /// A wrapper around start/await process.exitCode that will display the /// output of the executable continuously and fail on non-zero exit codes. @@ -175,20 +168,21 @@ class SubprocessLauncher { /// and their associated JSON objects. Future>> runStreamed( String executable, List arguments, - {String workingDirectory, - Map environment, + {String? workingDirectory, + Map? environment, bool includeParentEnvironment = true, - void Function(String) perLine}) async { - environment ??= {}; - environment.addAll(environmentDefaults); - List> jsonObjects; + void Function(String)? perLine}) async { + environment = {} + ..addAll(environmentDefaults) + ..addAll(environment ?? {}); + List> jsonObjects = []; /// Allow us to pretend we didn't pass the JSON flag in to dartdoc by /// printing what dartdoc would have printed without it, yet storing /// json objects into [jsonObjects]. Iterable jsonCallback(String line) { if (perLine != null) perLine(line); - Map result; + Map? result; try { result = json.decoder.convert(line); } on FormatException { @@ -198,10 +192,9 @@ class SubprocessLauncher { // line. Just ignore it and leave result null. } if (result != null) { - jsonObjects ??= []; jsonObjects.add(result); if (result.containsKey('message')) { - line = result['message']; + line = result['message'] as String; } else if (result.containsKey('data')) { var data = result['data'] as Map; line = data['text']; @@ -212,12 +205,12 @@ class SubprocessLauncher { stderr.write('$prefix+ '); if (workingDirectory != null) stderr.write('(cd "$workingDirectory" && '); - if (environment != null) { - stderr.write(environment.keys.map((String key) { - if (environment[key].contains(_quotables)) { - return "$key='${environment[key]}'"; + if (environment.isNotEmpty) { + stderr.write(environment.entries.map((MapEntry entry) { + if (entry.key.contains(_quotables)) { + return "${entry.key}='${entry.value}'"; } else { - return '$key=${environment[key]}'; + return '${entry.key}=${entry.value}'; } }).join(' ')); stderr.write(' '); @@ -235,7 +228,7 @@ class SubprocessLauncher { if (workingDirectory != null) stderr.write(')'); stderr.write('\n'); - if (Platform.environment.containsKey('DRY_RUN')) return null; + if (Platform.environment.containsKey('DRY_RUN')) return {}; var realExecutable = executable; var realArguments = []; From cd518ea67da7f7e77ed1b2f22f721330ce8a7700 Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Wed, 13 Oct 2021 12:36:49 -0700 Subject: [PATCH 4/6] subprocess --- tool/subprocess_launcher.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tool/subprocess_launcher.dart b/tool/subprocess_launcher.dart index f8770f0844..783c231835 100644 --- a/tool/subprocess_launcher.dart +++ b/tool/subprocess_launcher.dart @@ -69,7 +69,7 @@ class CoverageSubprocessLauncher extends SubprocessLauncher { } @override - Future>> runStreamed( + Future>> runStreamed( String executable, List arguments, {String? workingDirectory, Map? environment, @@ -166,7 +166,7 @@ class SubprocessLauncher { /// Windows (though some of the bashisms will no longer make sense). /// TODO(jcollins-g): refactor to return a stream of stderr/stdout lines /// and their associated JSON objects. - Future>> runStreamed( + Future>> runStreamed( String executable, List arguments, {String? workingDirectory, Map? environment, @@ -175,14 +175,14 @@ class SubprocessLauncher { environment = {} ..addAll(environmentDefaults) ..addAll(environment ?? {}); - List> jsonObjects = []; + List> jsonObjects = []; /// Allow us to pretend we didn't pass the JSON flag in to dartdoc by /// printing what dartdoc would have printed without it, yet storing /// json objects into [jsonObjects]. Iterable jsonCallback(String line) { if (perLine != null) perLine(line); - Map? result; + Map? result; try { result = json.decoder.convert(line); } on FormatException { From 574f9e0e311e5d05974898b7c71bda0f7a58498d Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Wed, 13 Oct 2021 12:54:49 -0700 Subject: [PATCH 5/6] type adjustment in grinder --- tool/grind.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tool/grind.dart b/tool/grind.dart index a1ebc02b6b..c8653315b9 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -553,7 +553,7 @@ Future testWithAnalyzerSdk() async { workingDirectory: sdkDartdoc); } -Future>> _buildSdkDocs( +Future>> _buildSdkDocs( String sdkDocsPath, Future futureCwd, [String label]) async { label ??= ''; @@ -576,7 +576,7 @@ Future>> _buildSdkDocs( workingDirectory: cwd); } -Future>> _buildTestPackageDocs( +Future>> _buildTestPackageDocs( String outputDir, String cwd, {List params, String label = '', String testPackagePath}) async { if (label != '') label = '-$label'; @@ -924,7 +924,7 @@ class FlutterRepo { SubprocessLauncher launcher; } -Future>> _buildFlutterDocs( +Future>> _buildFlutterDocs( String flutterPath, Future futureCwd, Map env, [String label]) async { var flutterRepo = await FlutterRepo.copyFromExistingFlutterRepo( From 51931908c0900bdf36a3ae481bbada29aab4c110 Mon Sep 17 00:00:00 2001 From: Janice Collins Date: Wed, 20 Oct 2021 13:11:50 -0700 Subject: [PATCH 6/6] comments --- tool/subprocess_launcher.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tool/subprocess_launcher.dart b/tool/subprocess_launcher.dart index 783c231835..c0ada7434d 100644 --- a/tool/subprocess_launcher.dart +++ b/tool/subprocess_launcher.dart @@ -14,9 +14,7 @@ import 'package:path/path.dart' as p; class CoverageSubprocessLauncher extends SubprocessLauncher { CoverageSubprocessLauncher(String context, [Map? environment]) : super( - context, - (environment ?? {}) - ..addAll({'DARTDOC_COVERAGE_DATA': tempDir.path})); + context, {...?environment, 'DARTDOC_COVERAGE_DATA': tempDir.path}); static int nextRun = 0;