diff --git a/lib/options.dart b/lib/options.dart index 85fdfa67e0..195510823e 100644 --- a/lib/options.dart +++ b/lib/options.dart @@ -10,8 +10,8 @@ import 'package:dartdoc/src/package_meta.dart'; /// Helper class that consolidates option contexts for instantiating generators. class DartdocGeneratorOptionContext extends DartdocOptionContext { - DartdocGeneratorOptionContext(DartdocOptionSet optionSet, Folder? dir, - ResourceProvider resourceProvider) + DartdocGeneratorOptionContext( + DartdocOptionSet optionSet, Folder dir, ResourceProvider resourceProvider) : super(optionSet, dir, resourceProvider); DartdocGeneratorOptionContext.fromDefaultContextLocation( DartdocOptionSet optionSet, ResourceProvider resourceProvider) @@ -51,8 +51,8 @@ class DartdocGeneratorOptionContext extends DartdocOptionContext { class DartdocProgramOptionContext extends DartdocGeneratorOptionContext with LoggingContext { - DartdocProgramOptionContext(DartdocOptionSet optionSet, Folder? dir, - ResourceProvider resourceProvider) + DartdocProgramOptionContext( + DartdocOptionSet optionSet, Folder dir, ResourceProvider resourceProvider) : super(optionSet, dir, resourceProvider); DartdocProgramOptionContext.fromDefaultContextLocation( DartdocOptionSet optionSet, ResourceProvider resourceProvider) @@ -84,7 +84,7 @@ Future parseOptions( List arguments, { OptionGenerator? additionalOptions, }) async { - var optionSet = await DartdocOptionSet.fromOptionGenerators( + var optionRoot = await DartdocOptionRoot.fromOptionGenerators( 'dartdoc', [ createDartdocOptions, @@ -96,22 +96,22 @@ Future parseOptions( packageMetaProvider); try { - optionSet.parseArguments(arguments); + optionRoot.parseArguments(arguments); } on FormatException catch (e) { stderr.writeln(' fatal error: ${e.message}'); stderr.writeln(''); - _printUsage(optionSet.argParser); + _printUsage(optionRoot.argParser); // Do not use exit() as this bypasses --pause-isolates-on-exit exitCode = 64; return null; } - if (optionSet['help'].valueAtCurrent()) { - _printHelp(optionSet.argParser); + if (optionRoot['help'].valueAtCurrent()) { + _printHelp(optionRoot.argParser); exitCode = 0; return null; } - if (optionSet['version'].valueAtCurrent()) { - _printVersion(optionSet.argParser); + if (optionRoot['version'].valueAtCurrent()) { + _printVersion(optionRoot.argParser); exitCode = 0; return null; } @@ -119,12 +119,12 @@ Future parseOptions( DartdocProgramOptionContext config; try { config = DartdocProgramOptionContext.fromDefaultContextLocation( - optionSet, packageMetaProvider.resourceProvider); + optionRoot, packageMetaProvider.resourceProvider); } on DartdocOptionError catch (e) { stderr.writeln(' fatal error: ${e.message}'); stderr.writeln(''); await stderr.flush(); - _printUsage(optionSet.argParser); + _printUsage(optionRoot.argParser); exitCode = 64; return null; } diff --git a/lib/src/dartdoc_options.dart b/lib/src/dartdoc_options.dart index a5ecfca71c..544e32dcdf 100644 --- a/lib/src/dartdoc_options.dart +++ b/lib/src/dartdoc_options.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 - /// /// dartdoc's dartdoc_options.yaml configuration file follows similar loading /// semantics to that of analysis_options.yaml, @@ -43,7 +41,7 @@ const bool _kBoolVal = true; const String compileArgsTagName = 'compile_args'; -int get _usageLineLength => stdout.hasTerminal ? stdout.terminalColumns : null; +int get _usageLineLength => stdout.hasTerminal ? stdout.terminalColumns : 80; typedef ConvertYamlToType = T Function(YamlMap, String, ResourceProvider); @@ -63,11 +61,11 @@ class CategoryDefinition { final String name; /// Displayed name of the category in docs, or null if there is none. - final String _displayName; + final String? _displayName; /// Canonical path of the markdown file used to document this category /// (or null if undocumented). - final String documentationMarkdown; + final String? documentationMarkdown; CategoryDefinition(this.name, this._displayName, this.documentationMarkdown); @@ -92,12 +90,10 @@ class CategoryConfiguration { var newCategoryDefinitions = {}; for (var entry in yamlMap.entries) { var name = entry.key.toString(); - String displayName; - String documentationMarkdown; var categoryMap = entry.value; if (categoryMap is Map) { - displayName = categoryMap['displayName']?.toString(); - documentationMarkdown = categoryMap['markdown']?.toString(); + var displayName = categoryMap['displayName']?.toString(); + var documentationMarkdown = categoryMap['markdown']?.toString(); if (documentationMarkdown != null) { documentationMarkdown = resourceProvider.pathContext.canonicalize( resourceProvider.pathContext @@ -122,9 +118,7 @@ class ToolConfiguration { final ResourceProvider resourceProvider; - ToolRunner _runner; - - ToolRunner get runner => _runner ??= ToolRunner(this); + late ToolRunner runner = ToolRunner(this); ToolConfiguration._(this.tools, this.resourceProvider); @@ -140,14 +134,14 @@ class ToolConfiguration { for (var entry in yamlMap.entries) { var name = entry.key.toString(); var toolMap = entry.value; - List compileArgs; - String description; - List command; - List setupCommand; + String? description; + List? compileArgs; + List? command; + List? setupCommand; if (toolMap is Map) { - description = toolMap['description']?.toString(); - List findCommand([String prefix = '']) { - List command; + description = toolMap['description'].toString(); + List? findCommand([String prefix = '']) { + List? command; // If the command key is given, then it applies to all platforms. var commandFromKey = toolMap.containsKey('${prefix}command') ? '${prefix}command' @@ -183,8 +177,8 @@ class ToolConfiguration { command = findCommand(); setupCommand = findCommand('setup_'); - List findArgs() { - List args; + List? findArgs() { + List? args; if (toolMap.containsKey(compileArgsTagName)) { var compileArgs = toolMap[compileArgsTagName]; if (compileArgs is String) { @@ -289,7 +283,7 @@ class _OptionValueWithContext { String canonicalDirectoryPath; /// If non-null, the basename of the configuration file the value came from. - String definingFile; + String? definingFile; /// A [pathLib.Context] variable initialized with canonicalDirectoryPath. p.Context pathContext; @@ -298,10 +292,9 @@ class _OptionValueWithContext { /// /// [path] is the path where this value came from (not required to be /// canonical). - _OptionValueWithContext(this.value, String path, {this.definingFile}) { - canonicalDirectoryPath = p.canonicalize(path); - pathContext = p.Context(current: canonicalDirectoryPath); - } + _OptionValueWithContext(this.value, String path, {this.definingFile}) + : canonicalDirectoryPath = p.canonicalize(path), + pathContext = p.Context(current: p.canonicalize(path)); /// Assume value is a path, and attempt to resolve it. /// @@ -341,7 +334,7 @@ class _OptionValueWithContext { /// [DartdocOptionArgOnly], and [DartdocOptionFileOnly]. abstract class DartdocOption { /// This is the value returned if we couldn't find one otherwise. - final T defaultsTo; + final T? defaultsTo; /// Text string for help passed on in command line options. final String help; @@ -379,7 +372,7 @@ abstract class DartdocOption { } /// Closure to convert yaml data into some other structure. - ConvertYamlToType _convertYamlToType; + final ConvertYamlToType? _convertYamlToType; // The choice not to use reflection means there's some ugly type checking, // somewhat more ugly than we'd have to do anyway to automatically convert @@ -398,11 +391,6 @@ abstract class DartdocOption { bool get _isDouble => _kDoubleVal is T; - DartdocOption _parent; - - /// The parent of this DartdocOption, or null if this is the root. - DartdocOption get parent => _parent; - final Map __yamlAtCanonicalPathCache = {}; /// Implementation detail for [DartdocOptionFileOnly]. Make sure we use @@ -410,19 +398,6 @@ abstract class DartdocOption { Map get _yamlAtCanonicalPathCache => root.__yamlAtCanonicalPathCache; - final ArgParser __argParser = ArgParser(usageLineLength: _usageLineLength); - - ArgParser get argParser => root.__argParser; - - ArgResults __argResults; - - /// Parse these as string arguments (from argv) with the argument parser. - /// Call before calling [valueAt] for any [DartdocOptionArgOnly] or - /// [DartdocOptionArgFile] in this tree. - void _parseArguments(List arguments) { - __argResults = argParser.parse(arguments); - } - /// Throw [DartdocFileMissing] with a detailed error message indicating where /// the error came from when a file or directory option is missing. void _onMissing( @@ -432,14 +407,14 @@ abstract class DartdocOption { void _validatePaths(_OptionValueWithContext valueWithContext) { if (!mustExist) return; assert(isDir || isFile); - List resolvedPaths; + var resolvedPaths = []; var value = valueWithContext.value; if (value is String) { resolvedPaths = [valueWithContext.resolvedValue as String]; } else if (value is List) { resolvedPaths = valueWithContext.resolvedValue as List; } else if (value is Map) { - resolvedPaths = (valueWithContext.resolvedValue as Map).values.toList(); + resolvedPaths = [...(valueWithContext.resolvedValue as Map).values]; } else { assert( false, @@ -458,11 +433,11 @@ abstract class DartdocOption { /// For a [List] or [String] value, if [isDir] or [isFile] is set, /// resolve paths in value relative to canonicalPath. - T _handlePathsInContext(_OptionValueWithContext valueWithContext) { + T? _handlePathsInContext(_OptionValueWithContext? valueWithContext) { if (valueWithContext?.value == null || !(isDir || isFile || isGlob)) { return valueWithContext?.value; } - _validatePaths(valueWithContext); + _validatePaths(valueWithContext!); return valueWithContext.resolvedValue; } @@ -473,17 +448,14 @@ abstract class DartdocOption { ArgResults get _argResults => root.__argResults; - /// Set the parent of this [DartdocOption]. Do not call more than once. - set parent(DartdocOption newParent) { - assert(_parent == null); - _parent = newParent; - } + /// To avoid accessing early, call [add] on the option's parent before + /// looking up unless this is a [DartdocRootOption]. + late final DartdocOption parent; - /// The root [DartdocOption] containing this object, or [this] if the object - /// has no parent. - DartdocOption get root { + /// The [DartdocOptionRoot] containing this object. + DartdocOptionRoot get root { DartdocOption p = this; - while (p.parent != null) { + while (p is! DartdocOptionRoot) { p = p.parent; } return p; @@ -493,10 +465,11 @@ abstract class DartdocOption { Iterable get keys { var keyList = []; DartdocOption option = this; - while (option?.name != null) { + while (option is! DartdocOptionRoot) { keyList.add(option.name); option = option.parent; } + keyList.add(option.name); return keyList.reversed; } @@ -514,23 +487,15 @@ abstract class DartdocOption { /// type. If [mustExist] is true, will throw [DartdocFileMissing] for command /// line parameters and file paths in config files that don't point to /// corresponding files or directories. - T valueAt(Folder dir); + T? valueAt(Folder dir); /// Calls [valueAt] with the working directory at the start of the program. - T valueAtCurrent() => valueAt(_directoryCurrent); - - Folder __directoryCurrent; - Folder get _directoryCurrent => - __directoryCurrent ??= resourceProvider.getFolder(_directoryCurrentPath); - - String __directoryCurrentPath; - String get _directoryCurrentPath => - __directoryCurrentPath ??= resourceProvider.pathContext.current; + T? valueAtCurrent() => valueAt(_directoryCurrent); - /// Calls [valueAt] on the directory this element is defined in. - T valueAtElement(Element element) => - valueAt(resourceProvider.getFolder(resourceProvider.pathContext.normalize( - resourceProvider.pathContext.basename(element.source.fullName)))); + late final Folder _directoryCurrent = + resourceProvider.getFolder(_directoryCurrentPath); + late final String _directoryCurrentPath = + resourceProvider.pathContext.current; /// Adds a DartdocOption to the children of this DartdocOption. void add(DartdocOption option) { @@ -539,19 +504,20 @@ abstract class DartdocOption { 'Tried to add two children with the same name: ${option.name}'); } _children[option.name] = option; + // TODO(jcollins-g): Consider a stronger refactor that doesn't rely on + // post-construction setup for [parent]. option.parent = this; - option.traverse((option) => option._onAdd()); } - /// This method is guaranteed to be called when [this] or any parent is added. - void _onAdd() {} + /// This method is called when parsing options to set up the [ArgParser]. + void _addToArgParser(ArgParser argParser) {} /// Adds a list of dartdoc options to the children of this DartdocOption. void addAll(Iterable options) => options.forEach(add); /// Get the immediate child of this node named [name]. DartdocOption operator [](String name) { - return _children[name]; + return _children[name]!; } /// Apply the function [visit] to [this] and all children. @@ -565,7 +531,8 @@ abstract class DartdocOption { /// overridden by a file. class DartdocOptionFileSynth extends DartdocOption with DartdocSyntheticOption, _DartdocFileOption { - bool _parentDirOverridesChild; + @override + final bool parentDirOverridesChild; @override final T Function(DartdocSyntheticOption, Folder) _compute; @@ -574,15 +541,13 @@ class DartdocOptionFileSynth extends DartdocOption {bool mustExist = false, String help = '', OptionKind optionIs = OptionKind.other, - bool parentDirOverridesChild, - ConvertYamlToType convertYamlToType}) + this.parentDirOverridesChild = false, + ConvertYamlToType? convertYamlToType}) : super(name, null, help, optionIs, mustExist, convertYamlToType, - resourceProvider) { - _parentDirOverridesChild = parentDirOverridesChild; - } + resourceProvider); @override - T valueAt(Folder dir) { + T? valueAt(Folder dir) { var result = _valueAtFromFile(dir); if (result?.definingFile != null) { return _handlePathsInContext(result); @@ -599,37 +564,35 @@ class DartdocOptionFileSynth extends DartdocOption _onMissingFromSynthetic(valueWithContext, missingPath); } } - - @override - bool get parentDirOverridesChild => _parentDirOverridesChild; } /// A class that defaults to a value computed from a closure, but can /// be overridden on the command line. class DartdocOptionArgSynth extends DartdocOption with DartdocSyntheticOption, _DartdocArgOption { - String _abbr; - bool _hide; - bool _negatable; - bool _splitCommas; + @override + final String? abbr; + @override + final bool hide; + @override + final bool negatable; + @override + final bool splitCommas; @override final T Function(DartdocSyntheticOption, Folder) _compute; DartdocOptionArgSynth( String name, this._compute, ResourceProvider resourceProvider, - {String abbr, + {this.abbr, bool mustExist = false, String help = '', - bool hide = false, + this.hide = false, OptionKind optionIs = OptionKind.other, - bool negatable = false, - bool splitCommas}) + this.negatable = false, + this.splitCommas = false}) : super(name, null, help, optionIs, mustExist, null, resourceProvider) { - _hide = hide; - _negatable = negatable; - _splitCommas = splitCommas; - _abbr = abbr; + assert(T is! Iterable || splitCommas == true); } @override @@ -639,24 +602,12 @@ class DartdocOptionArgSynth extends DartdocOption } @override - T valueAt(Folder dir) { + T? valueAt(Folder dir) { if (_argResults.wasParsed(argName)) { return _valueAtFromArgs(); } return _valueAtFromSynthetic(dir); } - - @override - String get abbr => _abbr; - - @override - bool get hide => _hide; - - @override - bool get negatable => _negatable; - - @override - bool get splitCommas => _splitCommas; } /// A synthetic option takes a closure at construction time that computes @@ -681,9 +632,9 @@ abstract class DartdocSyntheticOption implements DartdocOption { T Function(DartdocSyntheticOption, Folder) get _compute; @override - T valueAt(Folder dir) => _valueAtFromSynthetic(dir); + T? valueAt(Folder dir) => _valueAtFromSynthetic(dir); - T _valueAtFromSynthetic(Folder dir) { + T? _valueAtFromSynthetic(Folder dir) { var context = _OptionValueWithContext(_compute(this, dir), dir.path); return _handlePathsInContext(context); } @@ -705,12 +656,13 @@ abstract class DartdocSyntheticOption implements DartdocOption { typedef OptionGenerator = Future> Function( PackageMetaProvider); -/// A [DartdocOption] that only contains other [DartdocOption]s and is not an -/// option itself. -class DartdocOptionSet extends DartdocOption { - DartdocOptionSet(String name, ResourceProvider resourceProvider) - : super( - name, null, null, OptionKind.other, false, null, resourceProvider); +/// This is a [DartdocOptionSet] used as a root node. +class DartdocOptionRoot extends DartdocOptionSet { + DartdocOptionRoot(String name, ResourceProvider resourceProvider) + : super(name, resourceProvider); + + late final ArgParser _argParser = + ArgParser(usageLineLength: _usageLineLength); /// Asynchronous factory that is the main entry point to initialize Dartdoc /// options for use. @@ -718,26 +670,37 @@ class DartdocOptionSet extends DartdocOption { /// [name] is the top level key for the option set. /// [optionGenerators] is a sequence of asynchronous functions that return /// [DartdocOption]s that will be added to the new option set. - static Future fromOptionGenerators( + static Future fromOptionGenerators( String name, Iterable optionGenerators, PackageMetaProvider packageMetaProvider) async { var optionSet = - DartdocOptionSet(name, packageMetaProvider.resourceProvider); + DartdocOptionRoot(name, packageMetaProvider.resourceProvider); for (var generator in optionGenerators) { optionSet.addAll(await generator(packageMetaProvider)); } return optionSet; } - /// [DartdocOptionSet] always has the null value. - @override - void valueAt(Folder dir) => null; + ArgParser get argParser => _argParser; - /// Since we have no value, [_onMissing] does nothing. - @override - void _onMissing( - _OptionValueWithContext valueWithContext, String missingFilename) {} + /// Initialized via [_parseArguments]. + late ArgResults __argResults; + + bool _argParserInitialized = false; + + /// Parse these as string arguments (from argv) with the argument parser. + /// Call before calling [valueAt] for any [DartdocOptionArgOnly] or + /// [DartdocOptionArgFile] in this tree. + void _parseArguments(List arguments) { + if (!_argParserInitialized) { + _argParserInitialized = true; + traverse((DartdocOption option) { + option._addToArgParser(argParser); + }); + } + __argResults = argParser.parse(arguments); + } /// Traverse skips this node, because it doesn't represent a real /// configuration object. @@ -745,75 +708,81 @@ class DartdocOptionSet extends DartdocOption { void traverse(void Function(DartdocOption option) visitor) { _children.values.forEach((d) => d.traverse(visitor)); } + + @override + DartdocOption get parent => + throw UnsupportedError('Root nodes have no parent'); +} + +/// A [DartdocOption] that only contains other [DartdocOption]s and is not an +/// option itself. +class DartdocOptionSet extends DartdocOption { + DartdocOptionSet(String name, ResourceProvider resourceProvider) + : super(name, null, '', OptionKind.other, false, null, resourceProvider); + + /// [DartdocOptionSet] always has the null value. + @override + void valueAt(Folder dir) => null; + + /// Since we have no value, [_onMissing] does nothing. + @override + void _onMissing( + _OptionValueWithContext valueWithContext, String missingFilename) {} } /// A [DartdocOption] that only exists as a command line argument. `--help` is a /// good example. class DartdocOptionArgOnly extends DartdocOption with _DartdocArgOption { - String _abbr; - bool _hide; - bool _negatable; - bool _splitCommas; + @override + final String? abbr; + @override + final bool hide; + @override + final bool negatable; + @override + final bool splitCommas; DartdocOptionArgOnly( String name, T defaultsTo, ResourceProvider resourceProvider, - {String abbr, + {this.abbr, bool mustExist = false, String help = '', - bool hide = false, + this.hide = false, OptionKind optionIs = OptionKind.other, - bool negatable = false, - bool splitCommas}) + this.negatable = false, + this.splitCommas = false}) : super(name, defaultsTo, help, optionIs, mustExist, null, - resourceProvider) { - _hide = hide; - _negatable = negatable; - _splitCommas = splitCommas; - _abbr = abbr; - } - - @override - String get abbr => _abbr; - - @override - bool get hide => _hide; - - @override - bool get negatable => _negatable; - - @override - bool get splitCommas => _splitCommas; + resourceProvider); } /// A [DartdocOption] that works with command line arguments and /// `dartdoc_options` files. class DartdocOptionArgFile extends DartdocOption with _DartdocArgOption, _DartdocFileOption { - String _abbr; - bool _hide; - bool _negatable; - bool _parentDirOverridesChild; - bool _splitCommas; + @override + final String? abbr; + @override + final bool hide; + @override + final bool negatable; + @override + final bool parentDirOverridesChild; + @override + final bool splitCommas; DartdocOptionArgFile( String name, T defaultsTo, ResourceProvider resourceProvider, - {String abbr, + {this.abbr, bool mustExist = false, String help = '', - bool hide = false, + this.hide = false, OptionKind optionIs = OptionKind.other, - bool negatable = false, - bool parentDirOverridesChild = false, - bool splitCommas}) + this.negatable = false, + this.parentDirOverridesChild = false, + this.splitCommas = false}) : super(name, defaultsTo, help, optionIs, mustExist, null, - resourceProvider) { - _abbr = abbr; - _hide = hide; - _negatable = negatable; - _parentDirOverridesChild = parentDirOverridesChild; - _splitCommas = splitCommas; - } + resourceProvider); @override void _onMissing( @@ -828,47 +797,28 @@ class DartdocOptionArgFile extends DartdocOption /// Try to find an explicit argument setting this value, but if not, fall back /// to files finally, the default. @override - T valueAt(Folder dir) { + T? valueAt(Folder dir) { var value = _valueAtFromArgs(); value ??= _valueAtFromFiles(dir); value ??= defaultsTo; return value; } - - @override - String get abbr => _abbr; - - @override - bool get hide => _hide; - - @override - bool get negatable => _negatable; - - @override - bool get parentDirOverridesChild => _parentDirOverridesChild; - - @override - bool get splitCommas => _splitCommas; } class DartdocOptionFileOnly extends DartdocOption with _DartdocFileOption { - bool _parentDirOverridesChild; + @override + final bool parentDirOverridesChild; DartdocOptionFileOnly( String name, T defaultsTo, ResourceProvider resourceProvider, {bool mustExist = false, String help = '', OptionKind optionIs = OptionKind.other, - bool parentDirOverridesChild = false, - ConvertYamlToType convertYamlToType}) + this.parentDirOverridesChild = false, + ConvertYamlToType? convertYamlToType}) : super(name, defaultsTo, help, optionIs, mustExist, convertYamlToType, - resourceProvider) { - _parentDirOverridesChild = parentDirOverridesChild; - } - - @override - bool get parentDirOverridesChild => _parentDirOverridesChild; + resourceProvider); } /// Implements checking for options contained in dartdoc.yaml. @@ -905,18 +855,18 @@ abstract class _DartdocFileOption implements DartdocOption { /// Searches for a value in configuration files relative to [dir], and if not /// found, returns [defaultsTo]. - T valueAt(Folder dir) { + T? valueAt(Folder dir) { return _valueAtFromFiles(dir) ?? defaultsTo; } - final Map __valueAtFromFiles = {}; + final Map __valueAtFromFiles = {}; // The value of this option from files will not change unless files are // modified during execution (not allowed in Dartdoc). - T _valueAtFromFiles(Folder dir) { + T? _valueAtFromFiles(Folder dir) { var key = resourceProvider.pathContext.canonicalize(dir.path); if (!__valueAtFromFiles.containsKey(key)) { - _OptionValueWithContext valueWithContext; + _OptionValueWithContext? valueWithContext; if (parentDirOverridesChild) { valueWithContext = _valueAtFromFilesLastFound(dir); } else { @@ -929,8 +879,8 @@ abstract class _DartdocFileOption implements DartdocOption { /// Searches all dartdoc_options files through parent directories, starting at /// [dir], for the option and returns one once found. - _OptionValueWithContext _valueAtFromFilesFirstFound(Folder folder) { - _OptionValueWithContext value; + _OptionValueWithContext? _valueAtFromFilesFirstFound(Folder folder) { + _OptionValueWithContext? value; for (var dir in folder.withAncestors) { value = _valueAtFromFile(dir); if (value != null) break; @@ -941,8 +891,8 @@ abstract class _DartdocFileOption implements DartdocOption { /// Searches all dartdoc_options files for the option, and returns the value /// in the top-most parent directory `dartdoc_options.yaml` file it is /// mentioned in. - _OptionValueWithContext _valueAtFromFilesLastFound(Folder folder) { - _OptionValueWithContext value; + _OptionValueWithContext? _valueAtFromFilesLastFound(Folder folder) { + _OptionValueWithContext? value; for (var dir in folder.withAncestors) { var tmpValue = _valueAtFromFile(dir); if (tmpValue != null) value = tmpValue; @@ -952,16 +902,16 @@ abstract class _DartdocFileOption implements DartdocOption { /// Returns null if not set in the YAML file in this directory (or its /// parents). - _OptionValueWithContext _valueAtFromFile(Folder dir) { + _OptionValueWithContext? _valueAtFromFile(Folder dir) { var yamlFileData = _yamlAtDirectory(dir); var contextPath = yamlFileData.canonicalDirectoryPath; - Object yamlData = yamlFileData.data ?? {}; + Object yamlData = yamlFileData.data; for (var key in keys) { if (yamlData is Map && !yamlData.containsKey(key)) return null; yamlData = (yamlData as Map)[key] ?? {}; } - Object returnData; + Object? returnData; if (_isListString) { if (yamlData is YamlList) { returnData = [ @@ -969,6 +919,7 @@ abstract class _DartdocFileOption implements DartdocOption { ]; } } else if (yamlData is YamlMap) { + var convertYamlToType = _convertYamlToType; // TODO(jcollins-g): This special casing is unfortunate. Consider // a refactor to extract yaml data conversion into closures 100% of the // time or find a cleaner way to do this. @@ -976,8 +927,8 @@ abstract class _DartdocFileOption implements DartdocOption { // A refactor probably would integrate resolvedValue for // _OptionValueWithContext into the return data here, and would not have // that be separate. - if (_isMapString && _convertYamlToType == null) { - _convertYamlToType = (YamlMap yamlMap, String canonicalYamlPath, + if (_isMapString && convertYamlToType == null) { + convertYamlToType = (YamlMap yamlMap, String canonicalYamlPath, ResourceProvider resourceProvider) { var returnData = {}; for (var entry in yamlMap.entries) { @@ -986,15 +937,15 @@ abstract class _DartdocFileOption implements DartdocOption { return returnData as T; }; } - if (_convertYamlToType == null) { + if (convertYamlToType == null) { throw DartdocOptionError( 'Unable to convert yaml to type for option: $fieldName, method not ' 'defined'); } var canonicalDirectoryPath = resourceProvider.pathContext.canonicalize(contextPath); - returnData = _convertYamlToType( - yamlData, canonicalDirectoryPath, resourceProvider); + returnData = + convertYamlToType(yamlData, canonicalDirectoryPath, resourceProvider); } else if (_isDouble) { if (yamlData is num) { returnData = yamlData.toDouble(); @@ -1018,7 +969,7 @@ abstract class _DartdocFileOption implements DartdocOption { var canonicalPath = resourceProvider.pathContext.canonicalize(folder.path); if (_yamlAtCanonicalPathCache.containsKey(canonicalPath)) { - yamlData = _yamlAtCanonicalPathCache[canonicalPath]; + yamlData = _yamlAtCanonicalPathCache[canonicalPath]!; break; } canonicalPaths.add(canonicalPath); @@ -1054,12 +1005,12 @@ abstract class _DartdocArgOption implements DartdocOption { /// For [ArgParser], set to a single character to have a short version of the /// command line argument. - String get abbr; + String? get abbr; /// valueAt for arguments ignores the [dir] parameter and only uses command /// line arguments and the current working directory to resolve the result. @override - T valueAt(Folder dir) => _valueAtFromArgs() ?? defaultsTo; + T? valueAt(Folder dir) => _valueAtFromArgs() ?? defaultsTo; /// For passing in to [int.parse] and [double.parse] `onError'. void _throwErrorForTypes(String value) { @@ -1070,6 +1021,9 @@ abstract class _DartdocArgOption implements DartdocOption { example = '32'; } else if (_isDouble) { example = '0.76'; + } else { + throw UnimplementedError( + 'Type for $name is not implemented in $_throwErrorForTypes'); } throw DartdocOptionError( 'Invalid argument value: --$argName, set to "$value", must be a ' @@ -1077,7 +1031,7 @@ abstract class _DartdocArgOption implements DartdocOption { } /// Returns null if no argument was given on the command line. - T _valueAtFromArgs() { + T? _valueAtFromArgs() { var valueWithContext = _valueAtFromArgsWithContext(); return _handlePathsInContext(valueWithContext); } @@ -1098,7 +1052,7 @@ abstract class _DartdocArgOption implements DartdocOption { /// the [argParser] and the working directory from [_directoryCurrent]. /// /// Throws [UnsupportedError] if [T] is not a supported type. - _OptionValueWithContext _valueAtFromArgsWithContext() { + _OptionValueWithContext? _valueAtFromArgsWithContext() { if (!_argResults.wasParsed(argName)) return null; T retval; @@ -1145,7 +1099,8 @@ abstract class _DartdocArgOption implements DartdocOption { final camelCaseRegexp = RegExp(r'([a-z])([A-Z])(?=([a-z]))'); argName = argName.replaceAllMapped(camelCaseRegexp, (Match m) { var before = m.group(1); - var after = m.group(2).toLowerCase(); + // Group 2 is not optional. + var after = m.group(2)!.toLowerCase(); return '$before-$after'; }); return argName; @@ -1155,14 +1110,14 @@ abstract class _DartdocArgOption implements DartdocOption { /// [ArgParser.addFlag], [ArgParser.addOption], or [ArgParser.addMultiOption] /// as appropriate for [T]. @override - void _onAdd() { + void _addToArgParser(ArgParser argParser) { if (_isBool) { argParser.addFlag(argName, abbr: abbr, defaultsTo: defaultsTo as bool, help: help, hide: hide, - negatable: negatable ?? false); + negatable: negatable); } else if (_isInt || _isDouble || _isString) { argParser.addOption(argName, abbr: abbr, @@ -1212,29 +1167,25 @@ class DartdocOptionContext extends DartdocOptionContextBase @override final DartdocOptionSet optionSet; @override - Folder context; + final Folder context; // TODO(jcollins-g): Allow passing in structured data to initialize a // [DartdocOptionContext]'s arguments instead of having to parse strings // via optionSet. DartdocOptionContext(this.optionSet, Resource contextLocation, - ResourceProvider resourceProvider) { - assert(contextLocation != null); - context = resourceProvider.getFolder(resourceProvider.pathContext - .canonicalize(contextLocation is File - ? contextLocation.parent2.path - : contextLocation.path)); - } + ResourceProvider resourceProvider) + : context = resourceProvider.getFolder(resourceProvider.pathContext + .canonicalize(contextLocation is File + ? contextLocation.parent2.path + : contextLocation.path)); /// Build a DartdocOptionContext via the 'inputDir' command line option. DartdocOptionContext.fromDefaultContextLocation( - this.optionSet, ResourceProvider resourceProvider) { - var current = resourceProvider.pathContext.current; - String inputDir = - optionSet['inputDir'].valueAt(resourceProvider.getFolder(current)) ?? - current; - context = resourceProvider.getFolder(inputDir); - } + this.optionSet, ResourceProvider resourceProvider) + : context = resourceProvider.getFolder(optionSet['inputDir'].valueAt( + resourceProvider + .getFolder(resourceProvider.pathContext.current)) ?? + resourceProvider.pathContext.current); /// Build a DartdocOptionContext from an analyzer element (using its source /// location). @@ -1282,13 +1233,9 @@ class DartdocOptionContext extends DartdocOptionContextBase String get examplePathPrefix => optionSet['examplePathPrefix'].valueAt(context); - /// A memoized calculation of exclusions. // TODO(srawlins): This memoization saved a lot of time in unit testing, but // is the first value in this class to be memoized. Memoize others? - /*late final*/ List _exclude; - - List get exclude => - _exclude ??= optionSet['exclude'].valueAt(context); + late final List exclude = optionSet['exclude'].valueAt(context); List get excludePackages => optionSet['excludePackages'].valueAt(context); @@ -1388,6 +1335,7 @@ Future> createDartdocOptions( 'in the current package or "include-external"', negatable: true), DartdocOptionArgFile>('categoryOrder', [], resourceProvider, + splitCommas: true, help: 'A list of categories (not package names) to place first when ' "grouping symbols on dartdoc's sidebar. Unmentioned categories are " 'sorted after these.'), @@ -1421,7 +1369,7 @@ Future> createDartdocOptions( return []; }, resourceProvider, help: 'Remove text from libraries with the following names.'), - DartdocOptionArgFile('examplePathPrefix', null, resourceProvider, + DartdocOptionArgFile('examplePathPrefix', null, resourceProvider, optionIs: OptionKind.dir, help: 'Prefix for @example paths.\n(defaults to the project root)', mustExist: true), @@ -1456,8 +1404,7 @@ Future> createDartdocOptions( negatable: true), DartdocOptionArgFile>('include', [], resourceProvider, help: 'Library names to generate docs for.', splitCommas: true), - DartdocOptionArgFile>( - 'includeExternal', null, resourceProvider, + DartdocOptionArgFile>('includeExternal', [], resourceProvider, optionIs: OptionKind.file, help: 'Additional (external) dart files to include; use "dir/fileName", ' @@ -1507,14 +1454,18 @@ Future> createDartdocOptions( // Prefer SDK check first, then pub cache check. var inSdk = packageMeta .sdkType(option.parent.parent['flutterRoot'].valueAt(dir)); + // Analyzer may be confused because package_meta still needs + // migrating. It can definitely return null. + // ignore: unnecessary_null_comparison if (inSdk != null) { Map sdks = option.parent['sdks'].valueAt(dir); - if (sdks.containsKey(inSdk)) return sdks[inSdk]; + if (sdks.containsKey(inSdk)) return sdks[inSdk]!; } var hostedAt = packageMeta.hostedAt; + // ignore: unnecessary_null_comparison if (hostedAt != null) { Map hostMap = option.parent['hosted'].valueAt(dir); - if (hostMap.containsKey(hostedAt)) return hostMap[hostedAt]; + if (hostMap.containsKey(hostedAt)) return hostMap[hostedAt]!; } return ''; }, resourceProvider, help: 'Url to use for this particular package.'), @@ -1534,6 +1485,7 @@ Future> createDartdocOptions( 'packageMeta', (DartdocSyntheticOption option, Folder dir) { var packageMeta = packageMetaProvider.fromDir(dir); + // ignore: unnecessary_null_comparison if (packageMeta == null) { throw DartdocOptionError( 'Unable to determine package for directory: ${dir.path}'); @@ -1543,17 +1495,18 @@ Future> createDartdocOptions( resourceProvider, ), DartdocOptionArgOnly>('packageOrder', [], resourceProvider, + splitCommas: true, help: 'A list of package names to place first when grouping libraries in ' 'packages. Unmentioned packages are sorted after these.'), DartdocOptionArgOnly('sdkDocs', false, resourceProvider, help: 'Generate ONLY the docs for the Dart SDK.'), - DartdocOptionArgSynth('sdkDir', - (DartdocSyntheticOption option, Folder dir) { + DartdocOptionArgSynth('sdkDir', + (DartdocSyntheticOption option, Folder dir) { if (!(option.parent['sdkDocs'].valueAt(dir) as bool) && (option.root['topLevelPackageMeta'].valueAt(dir) as PackageMeta) .requiresFlutter) { - String flutterRoot = option.root['flutterRoot'].valueAt(dir); + String? flutterRoot = option.root['flutterRoot'].valueAt(dir); if (flutterRoot == null) { // For now, return null. An error is reported in // [PackageBuilder.buildPackageGraph]. @@ -1574,6 +1527,7 @@ Future> createDartdocOptions( (DartdocSyntheticOption option, Folder dir) { var packageMeta = packageMetaProvider.fromDir( resourceProvider.getFolder(option.parent['inputDir'].valueAt(dir))); + // ignore: unnecessary_null_comparison if (packageMeta == null) { throw DartdocOptionError( 'Unable to generate documentation: no package found'); diff --git a/lib/src/generator/templates.runtime_renderers.dart b/lib/src/generator/templates.runtime_renderers.dart index 476d4695a1..578eaae7fc 100644 --- a/lib/src/generator/templates.runtime_renderers.dart +++ b/lib/src/generator/templates.runtime_renderers.dart @@ -14834,6 +14834,7 @@ const _invisibleGetters = { 'runtimeType', 'optionSet', 'context', + 'exclude', 'allowTools', 'ambiguousReexportScorerMinConfidence', 'autoIncludeDependencies', @@ -14841,7 +14842,6 @@ const _invisibleGetters = { 'categories', 'dropTextFrom', 'examplePathPrefix', - 'exclude', 'excludePackages', 'enhancedReferenceLookup', 'flutterRoot', diff --git a/lib/src/logging.dart b/lib/src/logging.dart index b56a8d39c1..6a673a0cd5 100644 --- a/lib/src/logging.dart +++ b/lib/src/logging.dart @@ -145,7 +145,7 @@ Future>> createLoggingOptions( negatable: true), DartdocOptionArgSynth('quiet', (DartdocSyntheticOption option, Folder dir) { - if (option.root['generateDocs']?.valueAt(dir) == false) { + if (option.parent['generateDocs'].valueAt(dir) == false) { return true; } return false; diff --git a/lib/src/warnings.dart b/lib/src/warnings.dart index f34b790e72..acd4aa272a 100644 --- a/lib/src/warnings.dart +++ b/lib/src/warnings.dart @@ -41,23 +41,28 @@ Future>> createPackageWarningOptions( // packages they don't control. DartdocOptionArgOnly>( 'allowWarningsInPackages', null, resourceProvider, + splitCommas: true, help: 'Package names to display warnings for (ignore all others if set).'), DartdocOptionArgOnly>( 'allowErrorsInPackages', null, resourceProvider, + splitCommas: true, help: 'Package names to display errors for (ignore all others if set)'), DartdocOptionArgOnly>( 'ignoreWarningsInPackages', null, resourceProvider, + splitCommas: true, help: 'Package names to ignore warnings for. Takes priority over ' 'allow-warnings-in-packages'), DartdocOptionArgOnly>( 'ignoreErrorsInPackages', null, resourceProvider, + splitCommas: true, help: 'Package names to ignore errors for. Takes priority over ' 'allow-errors-in-packages'), // Options for globally enabling/disabling warnings and errors across // packages. Loaded from dartdoc_options.yaml, but command line arguments // will override. DartdocOptionArgFile>('errors', null, resourceProvider, + splitCommas: true, help: 'Additional warning names to force as errors. Specify an empty ' 'list to force defaults (overriding dartdoc_options.yaml)\nDefaults:\n' + (packageWarningDefinitions.values @@ -68,6 +73,7 @@ Future>> createPackageWarningOptions( .map((d) => ' ${d.warningName}: ${d.shortHelp}') .join('\n')), DartdocOptionArgFile>('ignore', null, resourceProvider, + splitCommas: true, help: 'Additional warning names to ignore. Specify an empty list to ' 'force defaults (overriding dartdoc_options.yaml).\nDefaults:\n' + (packageWarningDefinitions.values @@ -78,6 +84,7 @@ Future>> createPackageWarningOptions( .map((d) => ' ${d.warningName}: ${d.shortHelp}') .join('\n')), DartdocOptionArgFile>('warnings', null, resourceProvider, + splitCommas: true, help: 'Additional warning names to show as warnings (instead of error or ' 'ignore, if not warning by default).\nDefaults:\n' + diff --git a/test/dartdoc_options_test.dart b/test/dartdoc_options_test.dart index 95f7d38bab..3f2a540962 100644 --- a/test/dartdoc_options_test.dart +++ b/test/dartdoc_options_test.dart @@ -43,10 +43,10 @@ class ConvertedOption { void main() { var resourceProvider = pubPackageMetaProvider.resourceProvider; - DartdocOptionSet dartdocOptionSetFiles; - DartdocOptionSet dartdocOptionSetArgs; - DartdocOptionSet dartdocOptionSetAll; - DartdocOptionSet dartdocOptionSetSynthetic; + DartdocOptionRoot dartdocOptionSetFiles; + DartdocOptionRoot dartdocOptionSetArgs; + DartdocOptionRoot dartdocOptionSetAll; + DartdocOptionRoot dartdocOptionSetSynthetic; Folder tempDir; Folder firstDir; Folder secondDir; @@ -59,7 +59,7 @@ void main() { File firstExisting; setUpAll(() { - dartdocOptionSetSynthetic = DartdocOptionSet('dartdoc', resourceProvider); + dartdocOptionSetSynthetic = DartdocOptionRoot('dartdoc', resourceProvider); dartdocOptionSetSynthetic.add( DartdocOptionArgFile('mySpecialInteger', 91, resourceProvider)); dartdocOptionSetSynthetic.add( @@ -86,7 +86,7 @@ void main() { return (option.root['vegetableLoader'].valueAt(dir) as List).first; }, resourceProvider, optionIs: OptionKind.file)); - dartdocOptionSetFiles = DartdocOptionSet('dartdoc', resourceProvider); + dartdocOptionSetFiles = DartdocOptionRoot('dartdoc', resourceProvider); dartdocOptionSetFiles.add(DartdocOptionFileOnly>( 'categoryOrder', [], resourceProvider)); dartdocOptionSetFiles @@ -122,7 +122,7 @@ void main() { convertYamlToType: ConvertedOption.fromYamlMap, )); - dartdocOptionSetArgs = DartdocOptionSet('dartdoc', resourceProvider); + dartdocOptionSetArgs = DartdocOptionRoot('dartdoc', resourceProvider); dartdocOptionSetArgs.add(DartdocOptionArgOnly( 'cauliflowerSystem', false, resourceProvider)); dartdocOptionSetArgs.add(DartdocOptionArgOnly( @@ -152,7 +152,7 @@ void main() { 'unimportantFile', 'whatever', resourceProvider, optionIs: OptionKind.file)); - dartdocOptionSetAll = DartdocOptionSet('dartdoc', resourceProvider); + dartdocOptionSetAll = DartdocOptionRoot('dartdoc', resourceProvider); dartdocOptionSetAll.add(DartdocOptionArgFile>( 'categoryOrder', [], resourceProvider)); dartdocOptionSetAll.add( diff --git a/test/documentation_comment_test.dart b/test/documentation_comment_test.dart index 1650f759d9..f297926ec7 100644 --- a/test/documentation_comment_test.dart +++ b/test/documentation_comment_test.dart @@ -53,7 +53,7 @@ void main() { sdkFolder, defaultSdk: mockSdk, ); - var optionSet = await DartdocOptionSet.fromOptionGenerators( + var optionSet = await DartdocOptionRoot.fromOptionGenerators( 'dartdoc', [createDartdocOptions], packageMetaProvider); optionSet.parseArguments([]); packageConfigProvider = FakePackageConfigProvider(); diff --git a/test/end2end/dartdoc_test.dart b/test/end2end/dartdoc_test.dart index 62f961e1cc..047dc158d8 100644 --- a/test/end2end/dartdoc_test.dart +++ b/test/end2end/dartdoc_test.dart @@ -49,7 +49,7 @@ final Folder _testPackageExperiments = /// the '--input' flag. Future _generatorContextFromArgv( List argv) async { - var optionSet = await DartdocOptionSet.fromOptionGenerators( + var optionSet = await DartdocOptionRoot.fromOptionGenerators( 'dartdoc', [ createDartdocOptions, @@ -73,8 +73,10 @@ void main() { Folder tempDir; setUpAll(() async { - var optionSet = await DartdocOptionSet.fromOptionGenerators( - 'dartdoc', [createLoggingOptions], pubPackageMetaProvider); + var optionSet = await DartdocOptionRoot.fromOptionGenerators( + 'dartdoc', + [createDartdocProgramOptions, createLoggingOptions], + pubPackageMetaProvider); optionSet.parseArguments([]); startLogging(DartdocLoggingOptionContext( optionSet, diff --git a/test/mustachio/renderers_output_test.dart b/test/mustachio/renderers_output_test.dart index 39c7dbf604..5f0071342f 100644 --- a/test/mustachio/renderers_output_test.dart +++ b/test/mustachio/renderers_output_test.dart @@ -21,7 +21,7 @@ import 'package:test/test.dart'; /// the '--input' flag. Future _generatorContextFromArgv( List argv) async { - var optionSet = await DartdocOptionSet.fromOptionGenerators( + var optionSet = await DartdocOptionRoot.fromOptionGenerators( 'dartdoc', [ createDartdocOptions, diff --git a/test/package_test.dart b/test/package_test.dart index 4c44da5178..183e00bb35 100644 --- a/test/package_test.dart +++ b/test/package_test.dart @@ -40,7 +40,7 @@ void main() { resourceProvider = packageMetaProvider.resourceProvider; sdkFolder = packageMetaProvider.defaultSdkDir; - optionSet = await DartdocOptionSet.fromOptionGenerators( + optionSet = await DartdocOptionRoot.fromOptionGenerators( 'dartdoc', [createDartdocOptions], packageMetaProvider); packageConfigProvider = utils.getTestPackageConfigProvider(sdkFolder.path); }); diff --git a/test/src/utils.dart b/test/src/utils.dart index 3636ddf3f3..46b0e60be7 100644 --- a/test/src/utils.dart +++ b/test/src/utils.dart @@ -36,7 +36,7 @@ final Folder testPackageToolError = _resourceProvider.getFolder(_pathContext /// [DartdocOptionSet] based on the current working directory. Future contextFromArgv( List argv, PackageMetaProvider packageMetaProvider) async { - var optionSet = await DartdocOptionSet.fromOptionGenerators( + var optionSet = await DartdocOptionRoot.fromOptionGenerators( 'dartdoc', [createDartdocOptions], packageMetaProvider); optionSet.parseArguments(argv); return DartdocOptionContext.fromDefaultContextLocation( diff --git a/test/warnings_test.dart b/test/warnings_test.dart index 6f63e6b497..f4f571abf9 100644 --- a/test/warnings_test.dart +++ b/test/warnings_test.dart @@ -56,7 +56,7 @@ dartdoc: }); setUp(() async { - optionSet = await DartdocOptionSet.fromOptionGenerators( + optionSet = await DartdocOptionRoot.fromOptionGenerators( 'dartdoc', [createDartdocOptions], pubPackageMetaProvider); });