simple fix driver tool

Work to support https://github.com/dart-lang/sdk/issues/44272.

(Sure to evolve w/ some testing but I think there's enough to iterate on.)


Change-Id: Ieaab218ad8850631f4b16838d3b3821eaf7bd747
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/175005
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Phil Quitslund <pquitslund@google.com>
diff --git a/pkg/dartdev/test/fix_driver_test.dart b/pkg/dartdev/test/fix_driver_test.dart
new file mode 100644
index 0000000..35ec72a
--- /dev/null
+++ b/pkg/dartdev/test/fix_driver_test.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test/test.dart';
+
+import '../tool/fix_driver.dart';
+import 'utils.dart';
+
+void main() {
+  group('Driver', _driver);
+}
+
+Future<FixOutput> runFix(List<String> args) async {
+  var runner = FixRunner(logger: CapturingLogger());
+  var result = await runner.runFix(args);
+  return FixOutput(result);
+}
+
+void _driver() {
+  TestProject p;
+  tearDown(() => p?.dispose());
+
+  test('no fixes', () async {
+    p = project(mainSrc: 'int get foo => 1;\n');
+    var result = await runFix(['--apply', p.dirPath]);
+    expect(result.stdout, contains('Nothing to fix!'));
+    expect(result.returnCode, 0);
+  });
+}
+
+class FixOutput {
+  final FixResult<CapturingLogger> result;
+  FixOutput(this.result);
+
+  int get returnCode => result.returnCode;
+  String get stderr => result.logger.output.stderr.toString();
+  String get stdout => result.logger.output.stdout.toString();
+}
diff --git a/pkg/dartdev/test/test_all.dart b/pkg/dartdev/test/test_all.dart
index 2576ac9..edd644b 100644
--- a/pkg/dartdev/test/test_all.dart
+++ b/pkg/dartdev/test/test_all.dart
@@ -18,6 +18,7 @@
 import 'commands/test_test.dart' as test;
 import 'core_test.dart' as core;
 import 'experiments_test.dart' as experiments;
+import 'fix_driver_test.dart' as fix_driver;
 import 'no_such_file_test.dart' as no_such_file;
 import 'sdk_test.dart' as sdk;
 import 'smoke/implicit_smoke_test.dart' as implicit_smoke;
@@ -32,6 +33,7 @@
     create.main();
     experiments.main();
     fix.main();
+    fix_driver.main();
     flag.main();
     format.main();
     help.main();
diff --git a/pkg/dartdev/tool/fix_driver.dart b/pkg/dartdev/tool/fix_driver.dart
new file mode 100644
index 0000000..17f246a
--- /dev/null
+++ b/pkg/dartdev/tool/fix_driver.dart
@@ -0,0 +1,140 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
+import 'package:cli_util/cli_logging.dart';
+import 'package:dartdev/src/commands/fix.dart';
+import 'package:dartdev/src/core.dart';
+import 'package:dartdev/src/utils.dart';
+import 'package:meta/meta.dart';
+
+Future<void> main(List<String> args) async {
+  var runner = FixRunner(logger: Logger.standard());
+  var result = await runner.runFix(args);
+  return result.returnCode;
+}
+
+class CapturedProgress extends Progress {
+  final LoggerOutput output;
+
+  bool canceled = false;
+  bool finished = false;
+
+  CapturedProgress(this.output, String message) : super(message) {
+    output.progress.writeln(message);
+  }
+
+  @override
+  void cancel() {
+    canceled = true;
+  }
+
+  @override
+  void finish({String message, bool showTiming = false}) {
+    // todo (pq): consider capturing / tracking finish display updates.
+    finished = true;
+  }
+}
+
+class CapturingLogger implements Logger {
+  final LoggerOutput output = LoggerOutput();
+
+  @override
+  final Ansi ansi = Ansi(Ansi.terminalSupportsAnsi);
+
+  @override
+  bool isVerbose;
+
+  CapturingLogger({this.isVerbose = false});
+
+  @override
+  void flush() {
+    // deprecated.
+  }
+
+  @override
+  Progress progress(String message) => CapturedProgress(output, message);
+
+  @override
+  void stderr(String message) {
+    output.stderr.writeln(message);
+  }
+
+  @override
+  void stdout(String message) {
+    output.stdout.writeln(message);
+  }
+
+  @override
+  void trace(String message) {
+    output.trace.writeln(message);
+  }
+
+  @override
+  void write(String message) {
+    output.stdout.write(message);
+  }
+
+  @override
+  void writeCharCode(int charCode) {
+    output.stdout.writeCharCode(charCode);
+  }
+}
+
+class FixResult<T extends Logger> {
+  /// The value returned by [FixCommand.run].
+  final int returnCode;
+
+  /// The logger used in driving fixes.
+  final T logger;
+
+  FixResult(this.logger, this.returnCode);
+}
+
+class FixRunner<T extends Logger> extends CommandRunner<int> {
+  final _supportedOptions = ['dry-run', 'apply'];
+
+  T logger;
+
+  @override
+  final ArgParser argParser = ArgParser(
+    usageLineLength: dartdevUsageLineLength,
+    allowTrailingOptions: false,
+  );
+
+  FixRunner({@required this.logger})
+      : super('fix_runner',
+            'A command-line utility for testing the `dart fix` command.') {
+    addCommand(FixCommand());
+    _supportedOptions.forEach(argParser.addOption);
+  }
+
+  @override
+  Future<int> runCommand(ArgResults topLevelResults) async {
+    var result = await super.runCommand(topLevelResults);
+    return result;
+  }
+
+  Future<FixResult<T>> runFix(List<String> args) async {
+    log = logger;
+    var argResults = argParser.parse(['fix', ...?args]);
+    var result = await runCommand(argResults);
+    return FixResult(logger, result);
+  }
+}
+
+class LoggerOutput {
+  /// Messages reported to progress.
+  final StringBuffer progress = StringBuffer();
+
+  /// Messages reported to stdout.
+  final StringBuffer stdout = StringBuffer();
+
+  /// Messages reported to stderr.
+  final StringBuffer stderr = StringBuffer();
+
+  /// Messages reported to trace.
+  final StringBuffer trace = StringBuffer();
+}