Skip to content
35 changes: 24 additions & 11 deletions lib/src/dartdoc_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -489,8 +490,8 @@ abstract class DartdocOption<T> {
/// 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) {
Expand Down Expand Up @@ -674,7 +675,7 @@ class DartdocOptionFileSynth<T> extends DartdocOption<T>
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;
}

Expand Down Expand Up @@ -721,7 +722,7 @@ class DartdocOptionArgSynth<T> extends DartdocOption<T>
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;
Expand Down Expand Up @@ -767,7 +768,7 @@ class DartdocOptionSyntheticOnly<T> extends DartdocOption<T>
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<T> implements DartdocOption<T> {
Expand Down Expand Up @@ -801,7 +802,7 @@ typedef Future<List<DartdocOption>> OptionGenerator();
/// A [DartdocOption] that only contains other [DartdocOption]s and is not an option itself.
class DartdocOptionSet extends DartdocOption<Null> {
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.
Expand Down Expand Up @@ -852,7 +853,7 @@ class DartdocOptionArgOnly<T> extends DartdocOption<T>
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;
Expand Down Expand Up @@ -888,7 +889,7 @@ class DartdocOptionArgFile<T> extends DartdocOption<T>
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;
Expand Down Expand Up @@ -938,7 +939,7 @@ class DartdocOptionFileOnly<T> extends DartdocOption<T>
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;
}
Expand Down Expand Up @@ -1258,12 +1259,22 @@ abstract class _DartdocArgOption<T> implements DartdocOption<T> {
}
}

/// 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
Expand Down Expand Up @@ -1561,5 +1572,7 @@ Future<List<DartdocOption>> 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());
}
39 changes: 39 additions & 0 deletions lib/src/experiment_options.dart
Original file line number Diff line number Diff line change
@@ -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<String> 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<List<DartdocOption>> createExperimentOptions() async {
return <DartdocOption>[
// TODO(jcollins-g): Consider loading experiment values from dartdoc_options.yaml?
new DartdocOptionArgOnly<List<String>>('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>(
'experimentStatus',
(option, dir) => new ExperimentStatus.fromStrings(
option.parent['enable-experiment'].valueAt(dir))),
];
}
2 changes: 1 addition & 1 deletion lib/src/logging.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
3 changes: 3 additions & 0 deletions lib/src/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
12 changes: 9 additions & 3 deletions test/dartdoc_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,15 @@ void main() {

test('help prints command line args', () async {
List<String> 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 {
Expand Down
68 changes: 68 additions & 0 deletions test/experiment_options_test.dart
Original file line number Diff line number Diff line change
@@ -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);
});
});
}