diff --git a/lib/src/dartdoc_options.dart b/lib/src/dartdoc_options.dart index 15d7a47504..bb95737670 100644 --- a/lib/src/dartdoc_options.dart +++ b/lib/src/dartdoc_options.dart @@ -20,6 +20,7 @@ import 'dart:io'; import 'package:analyzer/dart/element/element.dart'; import 'package:args/args.dart'; import 'package:dartdoc/dartdoc.dart'; +import 'package:dartdoc/src/experiment_options.dart'; import 'package:dartdoc/src/io_utils.dart'; import 'package:dartdoc/src/tool_runner.dart'; import 'package:dartdoc/src/tuple.dart'; @@ -489,8 +490,8 @@ abstract class DartdocOption { /// and requires that one of [isDir] or [isFile] is set. final bool mustExist; - DartdocOption._(this.name, this.defaultsTo, this.help, this.isDir, - this.isFile, this.mustExist, this._convertYamlToType) { + DartdocOption(this.name, this.defaultsTo, this.help, this.isDir, this.isFile, + this.mustExist, this._convertYamlToType) { assert(!(isDir && isFile)); if (isDir || isFile) assert(_isString || _isListString || _isMapString); if (mustExist) { @@ -674,7 +675,7 @@ class DartdocOptionFileSynth extends DartdocOption bool isFile = false, bool parentDirOverridesChild, T Function(YamlMap, pathLib.Context) convertYamlToType}) - : super._(name, null, help, isDir, isFile, mustExist, convertYamlToType) { + : super(name, null, help, isDir, isFile, mustExist, convertYamlToType) { _parentDirOverridesChild = parentDirOverridesChild; } @@ -721,7 +722,7 @@ class DartdocOptionArgSynth extends DartdocOption bool isFile = false, bool negatable = false, bool splitCommas}) - : super._(name, null, help, isDir, isFile, mustExist, null) { + : super(name, null, help, isDir, isFile, mustExist, null) { _hide = hide; _negatable = negatable; _splitCommas = splitCommas; @@ -767,7 +768,7 @@ class DartdocOptionSyntheticOnly extends DartdocOption String help = '', bool isDir = false, bool isFile = false}) - : super._(name, null, help, isDir, isFile, mustExist, null); + : super(name, null, help, isDir, isFile, mustExist, null); } abstract class DartdocSyntheticOption implements DartdocOption { @@ -801,7 +802,7 @@ typedef Future> OptionGenerator(); /// A [DartdocOption] that only contains other [DartdocOption]s and is not an option itself. class DartdocOptionSet extends DartdocOption { DartdocOptionSet(String name) - : super._(name, null, null, false, false, false, null); + : super(name, null, null, false, false, false, null); /// Asynchronous factory that is the main entry point to initialize Dartdoc /// options for use. @@ -852,7 +853,7 @@ class DartdocOptionArgOnly extends DartdocOption bool isFile = false, bool negatable = false, bool splitCommas}) - : super._(name, defaultsTo, help, isDir, isFile, mustExist, null) { + : super(name, defaultsTo, help, isDir, isFile, mustExist, null) { _hide = hide; _negatable = negatable; _splitCommas = splitCommas; @@ -888,7 +889,7 @@ class DartdocOptionArgFile extends DartdocOption bool negatable = false, bool parentDirOverridesChild: false, bool splitCommas}) - : super._(name, defaultsTo, help, isDir, isFile, mustExist, null) { + : super(name, defaultsTo, help, isDir, isFile, mustExist, null) { _abbr = abbr; _hide = hide; _negatable = negatable; @@ -938,7 +939,7 @@ class DartdocOptionFileOnly extends DartdocOption bool isFile = false, bool parentDirOverridesChild: false, T Function(YamlMap, pathLib.Context) convertYamlToType}) - : super._(name, defaultsTo, help, isDir, isFile, mustExist, + : super(name, defaultsTo, help, isDir, isFile, mustExist, convertYamlToType) { _parentDirOverridesChild = parentDirOverridesChild; } @@ -1258,12 +1259,22 @@ abstract class _DartdocArgOption implements DartdocOption { } } +/// All DartdocOptionContext mixins should implement this, as well as any other +/// DartdocOptionContext mixins they use for calculating synthetic options. +abstract class DartdocOptionContextBase { + DartdocOptionSet get optionSet; + Directory get context; +} + /// An [DartdocOptionSet] wrapped in nice accessors specific to Dartdoc, which /// automatically passes in the right directory for a given context. Usually, /// a single [ModelElement], [Package], [Category] and so forth has a single context /// and so this can be made a member variable of those structures. -class DartdocOptionContext { +class DartdocOptionContext extends DartdocOptionContextBase + with DartdocExperimentOptionContext { + @override final DartdocOptionSet optionSet; + @override Directory context; // TODO(jcollins-g): Allow passing in structured data to initialize a @@ -1561,5 +1572,7 @@ Future> createDartdocOptions() async { 'exist. Executables for different platforms are specified by ' 'giving the platform name as a key, and a list of strings as the ' 'command.'), - ]; + // TODO(jcollins-g): refactor so there is a single static "create" for + // each DartdocOptionContext that traverses the inheritance tree itself. + ]..addAll(await createExperimentOptions()); } diff --git a/lib/src/experiment_options.dart b/lib/src/experiment_options.dart new file mode 100644 index 0000000000..0d9fa2f410 --- /dev/null +++ b/lib/src/experiment_options.dart @@ -0,0 +1,39 @@ +// Copyright (c) 2018, 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. + +/// +/// Implementation of Dart language experiment option handling for dartdoc. +/// See https://github.com/dart-lang/sdk/blob/master/docs/process/experimental-flags.md. +/// +library dartdoc.experiment_options; + +import 'package:analyzer/src/dart/analysis/experiments.dart'; +import 'package:dartdoc/src/dartdoc_options.dart'; + +abstract class DartdocExperimentOptionContext + implements DartdocOptionContextBase { + List get enableExperiment => + optionSet['enable-experiment'].valueAt(context); + ExperimentStatus get experimentStatus => + optionSet['experimentStatus'].valueAt(context); +} + +// TODO(jcollins-g): Implement YAML parsing for these flags and generation +// of [DartdocExperimentOptionContext], once a YAML file is available. +Future> createExperimentOptions() async { + return [ + // TODO(jcollins-g): Consider loading experiment values from dartdoc_options.yaml? + new DartdocOptionArgOnly>('enable-experiment', [], + help: 'Enable or disable listed experiments.\n' + + ExperimentStatus.knownFeatures.values + .where((e) => e.documentation != null) + .map((e) => + ' [no-]${e.enableString}: ${e.documentation} (default: ${e.isEnabledByDefault})') + .join('\n')), + new DartdocOptionSyntheticOnly( + 'experimentStatus', + (option, dir) => new ExperimentStatus.fromStrings( + option.parent['enable-experiment'].valueAt(dir))), + ]; +} diff --git a/lib/src/logging.dart b/lib/src/logging.dart index 103be7e85c..c52b05f12b 100644 --- a/lib/src/logging.dart +++ b/lib/src/logging.dart @@ -111,7 +111,7 @@ void startLogging(LoggingContext config) { } } -abstract class LoggingContext implements DartdocOptionContext { +abstract class LoggingContext implements DartdocOptionContextBase { bool get json => optionSet['json'].valueAt(context); bool get showProgress => optionSet['showProgress'].valueAt(context); } diff --git a/lib/src/model.dart b/lib/src/model.dart index 13bace5752..b72ee5b525 100644 --- a/lib/src/model.dart +++ b/lib/src/model.dart @@ -6458,6 +6458,9 @@ class PackageBuilder { AnalysisDriverScheduler scheduler = new AnalysisDriverScheduler(log); AnalysisOptionsImpl options = new AnalysisOptionsImpl(); + // TODO(jcollins-g): pass in an ExperimentStatus instead? + options.enabledExperiments = config.enableExperiment; + // TODO(jcollins-g): Make use of currently not existing API for managing // many AnalysisDrivers // TODO(jcollins-g): make use of DartProject isApi() diff --git a/test/dartdoc_test.dart b/test/dartdoc_test.dart index 0e405e8b79..ab02d48789 100644 --- a/test/dartdoc_test.dart +++ b/test/dartdoc_test.dart @@ -154,9 +154,15 @@ void main() { test('help prints command line args', () async { List outputLines = []; - await subprocessLauncher.runStreamed(Platform.resolvedExecutable, [dartdocPath, '--help'], perLine: outputLines.add); - expect(outputLines, contains('Generate HTML documentation for Dart libraries.')); - expect(outputLines.join('\n'), contains(new RegExp('^-h, --help[ ]+Show command help.', multiLine: true))) ; + await subprocessLauncher.runStreamed( + Platform.resolvedExecutable, [dartdocPath, '--help'], + perLine: outputLines.add); + expect(outputLines, + contains('Generate HTML documentation for Dart libraries.')); + expect( + outputLines.join('\n'), + contains(new RegExp('^-h, --help[ ]+Show command help.', + multiLine: true))); }); test('Validate missing FLUTTER_ROOT exception is clean', () async { diff --git a/test/experiment_options_test.dart b/test/experiment_options_test.dart new file mode 100644 index 0000000000..6772bfdbce --- /dev/null +++ b/test/experiment_options_test.dart @@ -0,0 +1,68 @@ +// Copyright (c) 2018, 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. + +/// Unit tests for lib/src/experiment_options.dart. +library dartdoc.experiment_options_test; + +import 'dart:io'; + +import 'package:dartdoc/src/dartdoc_options.dart'; +import 'package:dartdoc/src/experiment_options.dart'; +import 'package:path/path.dart' as pathLib; +import 'package:test/test.dart'; + +class DartdocExperimentOptionContextTester extends DartdocOptionContext { + DartdocExperimentOptionContextTester( + DartdocOptionSet optionSet, FileSystemEntity entity) + : super(optionSet, entity); +} + +void main() { + DartdocOptionSet experimentOptions; + Directory tempDir; + File optionsFile; + + setUp(() async { + experimentOptions = await DartdocOptionSet.fromOptionGenerators( + 'dartdoc', [createExperimentOptions]); + }); + + setUpAll(() { + tempDir = Directory.systemTemp.createTempSync('experiment_options_test'); + optionsFile = new File(pathLib.join(tempDir.path, 'dartdoc_options.yaml')) + ..createSync(); + optionsFile.writeAsStringSync(''' +dartdoc: + enable-experiment: + - constant-update-2018 + - fake-experiment + - no-fake-experiment-on +'''); + }); + + tearDownAll(() { + tempDir.deleteSync(recursive: true); + }); + + group('Experimental options test', () { + test('Defaults work for all options', () { + experimentOptions.parseArguments([]); + DartdocExperimentOptionContextTester tester = + new DartdocExperimentOptionContextTester( + experimentOptions, Directory.current); + expect(tester.experimentStatus.constant_update_2018, isFalse); + expect(tester.experimentStatus.set_literals, isFalse); + }); + + test('Overriding defaults works via args', () { + experimentOptions.parseArguments( + ['--enable-experiment', 'constant-update-2018,set-literals']); + DartdocExperimentOptionContextTester tester = + new DartdocExperimentOptionContextTester( + experimentOptions, Directory.current); + expect(tester.experimentStatus.constant_update_2018, isTrue); + expect(tester.experimentStatus.set_literals, isTrue); + }); + }); +}