mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 17:40:04 +00:00
Improve dartfix console output
Change-Id: Ifa55c6d03487a987cf6b0a4a6ae58d6068e54d8b Reviewed-on: https://dart-review.googlesource.com/c/80180 Commit-Queue: Dan Rubel <danrubel@google.com> Reviewed-by: Devon Carew <devoncarew@google.com>
This commit is contained in:
parent
da67d58110
commit
6066ac83e3
|
@ -6,10 +6,6 @@ import 'dart:io' as io;
|
|||
|
||||
/// The context for dartfix.
|
||||
class Context {
|
||||
StringSink get stdout => io.stdout;
|
||||
|
||||
StringSink get stderr => io.stderr;
|
||||
|
||||
String get workingDir => io.Directory.current.path;
|
||||
|
||||
bool exists(String filePath) =>
|
||||
|
@ -23,8 +19,4 @@ class Context {
|
|||
bool isDirectory(String filePath) =>
|
||||
io.FileSystemEntity.typeSync(filePath) ==
|
||||
io.FileSystemEntityType.directory;
|
||||
|
||||
void print([String text = '']) {
|
||||
stdout.writeln(text);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,32 +19,26 @@ import 'package:path/path.dart' as path;
|
|||
const runAnalysisServerFromSource = false;
|
||||
|
||||
class Driver {
|
||||
static const progressThreshold = 10;
|
||||
|
||||
final Context context = new Context();
|
||||
Context context;
|
||||
Logger logger;
|
||||
Server server;
|
||||
|
||||
Completer serverConnected;
|
||||
Completer analysisComplete;
|
||||
bool force;
|
||||
bool overwrite;
|
||||
bool verbose;
|
||||
List<String> targets;
|
||||
|
||||
Logger logger;
|
||||
|
||||
int progressCount = progressThreshold;
|
||||
|
||||
Ansi get ansi => logger.ansi;
|
||||
|
||||
Future start(List<String> args) async {
|
||||
final Options options = Options.parse(args, context);
|
||||
final Options options = Options.parse(args);
|
||||
|
||||
force = options.force;
|
||||
overwrite = options.overwrite;
|
||||
verbose = options.verbose;
|
||||
targets = options.targets;
|
||||
|
||||
context = options.context;
|
||||
logger = options.logger;
|
||||
|
||||
EditDartfixResult result;
|
||||
|
@ -53,8 +47,8 @@ class Driver {
|
|||
|
||||
bool normalShutdown = false;
|
||||
try {
|
||||
await setupAnalysis(options);
|
||||
result = await requestFixes(options);
|
||||
final progress = await setupAnalysis(options);
|
||||
result = await requestFixes(options, progress);
|
||||
normalShutdown = true;
|
||||
} finally {
|
||||
try {
|
||||
|
@ -82,14 +76,15 @@ class Driver {
|
|||
sdkPath: options.sdkPath, useSnapshot: !runAnalysisServerFromSource);
|
||||
server.listenToOutput(dispatchNotification);
|
||||
return serverConnected.future.timeout(connectTimeout, onTimeout: () {
|
||||
context.stderr.writeln('Failed to connect to server');
|
||||
logger.stderr('Failed to connect to server');
|
||||
context.exit(15);
|
||||
});
|
||||
}
|
||||
|
||||
Future setupAnalysis(Options options) async {
|
||||
context.stdout.write('${ansi.emphasized('Calculating fixes')}...');
|
||||
logger.trace('\nSetup analysis');
|
||||
Future<Progress> setupAnalysis(Options options) async {
|
||||
final progress = logger.progress('${ansi.emphasized('Calculating fixes')}');
|
||||
logger.trace('');
|
||||
logger.trace('Setup analysis');
|
||||
await server.send(SERVER_REQUEST_SET_SUBSCRIPTIONS,
|
||||
new ServerSetSubscriptionsParams([ServerService.STATUS]).toJson());
|
||||
await server.send(
|
||||
|
@ -98,15 +93,17 @@ class Driver {
|
|||
options.targets,
|
||||
const [],
|
||||
).toJson());
|
||||
return progress;
|
||||
}
|
||||
|
||||
Future<EditDartfixResult> requestFixes(Options options) async {
|
||||
Future<EditDartfixResult> requestFixes(
|
||||
Options options, Progress progress) async {
|
||||
logger.trace('Requesting fixes');
|
||||
analysisComplete = new Completer();
|
||||
Map<String, dynamic> json = await server.send(
|
||||
EDIT_REQUEST_DARTFIX, new EditDartfixParams(options.targets).toJson());
|
||||
await analysisComplete?.future;
|
||||
resetProgress();
|
||||
progress.finish(showTiming: true);
|
||||
ResponseDecoder decoder = new ResponseDecoder(null);
|
||||
return EditDartfixResult.fromJson(decoder, 'result', json);
|
||||
}
|
||||
|
@ -130,16 +127,16 @@ class Driver {
|
|||
result.otherRecommendations,
|
||||
);
|
||||
if (result.descriptionOfFixes.isEmpty) {
|
||||
context.print('');
|
||||
context.print(result.otherRecommendations.isNotEmpty
|
||||
logger.stdout('');
|
||||
logger.stdout(result.otherRecommendations.isNotEmpty
|
||||
? 'No recommended changes that cannot be automatically applied.'
|
||||
: 'No recommended changes.');
|
||||
return;
|
||||
}
|
||||
context.print('');
|
||||
context.print(ansi.emphasized('Files to be changed:'));
|
||||
logger.stdout('');
|
||||
logger.stdout(ansi.emphasized('Files to be changed:'));
|
||||
for (SourceFileEdit fileEdit in result.fixes) {
|
||||
context.print(' ${_relativePath(fileEdit.file)}');
|
||||
logger.stdout(' ${_relativePath(fileEdit.file)}');
|
||||
}
|
||||
if (shouldApplyChanges(result)) {
|
||||
for (SourceFileEdit fileEdit in result.fixes) {
|
||||
|
@ -150,33 +147,33 @@ class Driver {
|
|||
}
|
||||
await file.writeAsString(code);
|
||||
}
|
||||
context.print('Changes applied.');
|
||||
logger.stdout('Changes applied.');
|
||||
}
|
||||
}
|
||||
|
||||
void showDescriptions(String title, List<String> descriptions) {
|
||||
if (descriptions.isNotEmpty) {
|
||||
context.print('');
|
||||
context.print(ansi.emphasized('$title:'));
|
||||
logger.stdout('');
|
||||
logger.stdout(ansi.emphasized('$title:'));
|
||||
List<String> sorted = new List.from(descriptions)..sort();
|
||||
for (String line in sorted) {
|
||||
context.print(' $line');
|
||||
logger.stdout(' $line');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldApplyChanges(EditDartfixResult result) {
|
||||
context.print();
|
||||
logger.stdout('');
|
||||
if (result.hasErrors) {
|
||||
context.print('WARNING: The analyzed source contains errors'
|
||||
logger.stdout('WARNING: The analyzed source contains errors'
|
||||
' that may affect the accuracy of these changes.');
|
||||
context.print();
|
||||
logger.stdout('');
|
||||
if (!force) {
|
||||
context.print('Rerun with --$forceOption to apply these changes.');
|
||||
logger.stdout('Rerun with --$forceOption to apply these changes.');
|
||||
return false;
|
||||
}
|
||||
} else if (!overwrite && !force) {
|
||||
context.print('Rerun with --$overwriteOption to apply these changes.');
|
||||
logger.stdout('Rerun with --$overwriteOption to apply these changes.');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -292,18 +289,14 @@ class Driver {
|
|||
if (!shouldFilterError(error)) {
|
||||
if (!foundAtLeastOneError) {
|
||||
foundAtLeastOneError = true;
|
||||
resetProgress();
|
||||
context.print('${_relativePath(params.file)}:');
|
||||
logger.stdout('${_relativePath(params.file)}:');
|
||||
}
|
||||
Location loc = error.location;
|
||||
context.print(' ${_toSentenceFragment(error.message)}'
|
||||
logger.stdout(' ${_toSentenceFragment(error.message)}'
|
||||
' • ${loc.startLine}:${loc.startColumn}');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!foundAtLeastOneError) {
|
||||
showProgress();
|
||||
}
|
||||
}
|
||||
|
||||
void onServerConnected(ServerConnectedParams params) {
|
||||
|
@ -321,7 +314,7 @@ class Driver {
|
|||
if (params.stackTrace != null) {
|
||||
message.writeln(params.stackTrace);
|
||||
}
|
||||
context.stderr.writeln(message.toString());
|
||||
logger.stderr(message.toString());
|
||||
context.exit(15);
|
||||
}
|
||||
|
||||
|
@ -333,13 +326,6 @@ class Driver {
|
|||
}
|
||||
}
|
||||
|
||||
void resetProgress() {
|
||||
if (!verbose && progressCount >= progressThreshold) {
|
||||
context.print();
|
||||
}
|
||||
progressCount = 0;
|
||||
}
|
||||
|
||||
bool shouldFilterError(AnalysisError error) {
|
||||
// Do not show TODOs or errors that will be automatically fixed.
|
||||
|
||||
|
@ -350,13 +336,6 @@ class Driver {
|
|||
error.code == 'wrong_number_of_type_arguments_constructor';
|
||||
}
|
||||
|
||||
void showProgress() {
|
||||
if (!verbose && progressCount % progressThreshold == 0) {
|
||||
context.stdout.write('.');
|
||||
}
|
||||
++progressCount;
|
||||
}
|
||||
|
||||
bool isTarget(String filePath) {
|
||||
for (String target in targets) {
|
||||
if (filePath == target || path.isWithin(target, filePath)) {
|
||||
|
|
|
@ -11,23 +11,17 @@ import 'package:path/path.dart' as path;
|
|||
/// Command line options for `dartfix`.
|
||||
class Options {
|
||||
final Context context;
|
||||
|
||||
List<String> targets;
|
||||
bool force;
|
||||
bool overwrite;
|
||||
String sdkPath;
|
||||
bool verbose;
|
||||
bool useColor;
|
||||
|
||||
Logger logger;
|
||||
|
||||
Options(this.context);
|
||||
List<String> targets;
|
||||
String sdkPath;
|
||||
final bool force;
|
||||
final bool overwrite;
|
||||
final bool verbose;
|
||||
final bool useColor;
|
||||
|
||||
static Options parse(List<String> args, Context context, {Logger logger}) {
|
||||
Options options = new Options(context ?? new Context());
|
||||
final parser = new ArgParser(allowTrailingOptions: true);
|
||||
|
||||
parser
|
||||
static Options parse(List<String> args, {Context context, Logger logger}) {
|
||||
final parser = new ArgParser(allowTrailingOptions: true)
|
||||
..addFlag(overwriteOption,
|
||||
abbr: 'w',
|
||||
help: 'Overwrite files with the recommended changes.',
|
||||
|
@ -52,56 +46,19 @@ class Options {
|
|||
help: 'Use ansi colors when printing messages.',
|
||||
defaultsTo: Ansi.terminalSupportsAnsi);
|
||||
|
||||
context ??= new Context();
|
||||
|
||||
ArgResults results;
|
||||
try {
|
||||
results = parser.parse(args);
|
||||
} on FormatException catch (e) {
|
||||
context.stderr.writeln(e.message);
|
||||
_showUsage(parser, context);
|
||||
logger ??= new Logger.standard(ansi: new Ansi(Ansi.terminalSupportsAnsi));
|
||||
logger.stderr(e.message);
|
||||
_showUsage(parser, logger);
|
||||
context.exit(15);
|
||||
}
|
||||
|
||||
if (results[_helpOption] as bool) {
|
||||
_showUsage(parser, context);
|
||||
context.exit(1);
|
||||
}
|
||||
|
||||
options._fromArgs(results);
|
||||
|
||||
// Infer the Dart SDK location
|
||||
options.sdkPath = getSdkPath(args);
|
||||
String sdkPath = options.sdkPath;
|
||||
if (sdkPath == null) {
|
||||
context.stderr.writeln('No Dart SDK found.');
|
||||
_showUsage(parser, context);
|
||||
}
|
||||
if (!context.exists(sdkPath)) {
|
||||
context.stderr.writeln('Invalid Dart SDK path: $sdkPath');
|
||||
context.exit(15);
|
||||
}
|
||||
|
||||
// Check for files and/or directories to analyze.
|
||||
if (options.targets == null || options.targets.isEmpty) {
|
||||
context.stderr
|
||||
.writeln('Expected at least one file or directory to analyze.');
|
||||
_showUsage(parser, context);
|
||||
context.exit(15);
|
||||
}
|
||||
|
||||
// Normalize and verify paths
|
||||
options.targets =
|
||||
options.targets.map<String>(options.makeAbsoluteAndNormalize).toList();
|
||||
for (String target in options.targets) {
|
||||
if (!context.isDirectory(target)) {
|
||||
if (!context.exists(target)) {
|
||||
context.stderr.writeln('Target does not exist: $target');
|
||||
} else {
|
||||
context.stderr.writeln('Expected directory, but found: $target');
|
||||
}
|
||||
_showUsage(parser, context);
|
||||
context.exit(15);
|
||||
}
|
||||
}
|
||||
Options options = new Options._fromArgs(context, results);
|
||||
|
||||
if (logger == null) {
|
||||
if (options.verbose) {
|
||||
|
@ -115,26 +72,63 @@ class Options {
|
|||
));
|
||||
}
|
||||
}
|
||||
|
||||
options.logger = logger;
|
||||
|
||||
if (results[_helpOption] as bool) {
|
||||
_showUsage(parser, logger);
|
||||
context.exit(1);
|
||||
}
|
||||
|
||||
// Infer the Dart SDK location
|
||||
options.sdkPath = getSdkPath(args);
|
||||
String sdkPath = options.sdkPath;
|
||||
if (sdkPath == null) {
|
||||
logger.stderr('No Dart SDK found.');
|
||||
_showUsage(parser, logger);
|
||||
}
|
||||
if (!context.exists(sdkPath)) {
|
||||
logger.stderr('Invalid Dart SDK path: $sdkPath');
|
||||
context.exit(15);
|
||||
}
|
||||
|
||||
// Check for files and/or directories to analyze.
|
||||
if (options.targets == null || options.targets.isEmpty) {
|
||||
logger.stderr('Expected at least one file or directory to analyze.');
|
||||
_showUsage(parser, logger);
|
||||
context.exit(15);
|
||||
}
|
||||
|
||||
// Normalize and verify paths
|
||||
options.targets =
|
||||
options.targets.map<String>(options.makeAbsoluteAndNormalize).toList();
|
||||
for (String target in options.targets) {
|
||||
if (!context.isDirectory(target)) {
|
||||
if (!context.exists(target)) {
|
||||
logger.stderr('Target does not exist: $target');
|
||||
} else {
|
||||
logger.stderr('Expected directory, but found: $target');
|
||||
}
|
||||
_showUsage(parser, logger);
|
||||
context.exit(15);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.verbose) {
|
||||
context.print('Targets:');
|
||||
logger.trace('Targets:');
|
||||
for (String target in options.targets) {
|
||||
context.print(' $target');
|
||||
logger.trace(' $target');
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
void _fromArgs(ArgResults results) {
|
||||
targets = results.rest;
|
||||
force = results[forceOption] as bool;
|
||||
overwrite = results[overwriteOption] as bool;
|
||||
verbose = results[_verboseOption] as bool;
|
||||
useColor = results.wasParsed('color') ? results['color'] as bool : null;
|
||||
}
|
||||
Options._fromArgs(this.context, ArgResults results)
|
||||
: targets = results.rest,
|
||||
force = results[forceOption] as bool,
|
||||
overwrite = results[overwriteOption] as bool,
|
||||
verbose = results[_verboseOption] as bool,
|
||||
useColor = results.wasParsed('color') ? results['color'] as bool : null;
|
||||
|
||||
String makeAbsoluteAndNormalize(String target) {
|
||||
if (!path.isAbsolute(target)) {
|
||||
|
@ -143,11 +137,10 @@ class Options {
|
|||
return path.normalize(target);
|
||||
}
|
||||
|
||||
static _showUsage(ArgParser parser, Context context) {
|
||||
context.stderr
|
||||
.writeln('Usage: $_binaryName [options...] <directory paths>');
|
||||
context.stderr.writeln('');
|
||||
context.stderr.writeln(parser.usage);
|
||||
static _showUsage(ArgParser parser, Logger logger) {
|
||||
logger.stderr('Usage: $_binaryName [options...] <directory paths>');
|
||||
logger.stderr('');
|
||||
logger.stderr(parser.usage);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:analyzer_cli/src/fix/options.dart';
|
||||
import 'package:cli_util/cli_logging.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
|
@ -12,7 +11,12 @@ import 'test_context.dart';
|
|||
main() {
|
||||
group('Options', () {
|
||||
TestContext context;
|
||||
Logger logger;
|
||||
TestLogger logger;
|
||||
|
||||
setUp(() {
|
||||
context = new TestContext();
|
||||
logger = new TestLogger();
|
||||
});
|
||||
|
||||
String p(String filePath) => context.convertPath(filePath);
|
||||
|
||||
|
@ -29,13 +33,13 @@ main() {
|
|||
Options options;
|
||||
int actualExitCode;
|
||||
try {
|
||||
options = Options.parse(args, context, logger: logger);
|
||||
options = Options.parse(args, context: context, logger: logger);
|
||||
} on TestExit catch (e) {
|
||||
actualExitCode = e.code;
|
||||
}
|
||||
expect(context.stderr.toString(),
|
||||
expect(logger.stderrBuffer.toString(),
|
||||
errorOut != null ? contains(errorOut) : isEmpty);
|
||||
expect(context.stdout.toString(),
|
||||
expect(logger.stdoutBuffer.toString(),
|
||||
normalOut != null ? contains(normalOut) : isEmpty);
|
||||
if (exitCode != null) {
|
||||
expect(actualExitCode, exitCode, reason: 'exit code');
|
||||
|
@ -59,11 +63,6 @@ main() {
|
|||
return options;
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
context = new TestContext();
|
||||
logger = new _TestLogger(context);
|
||||
});
|
||||
|
||||
test('force', () {
|
||||
parse(['--force', 'foo'], force: true, targetSuffixes: ['foo']);
|
||||
});
|
||||
|
@ -77,6 +76,15 @@ main() {
|
|||
errorOut: 'Could not find an option named "foo"', exitCode: 15);
|
||||
});
|
||||
|
||||
test('invalid option no logger', () {
|
||||
try {
|
||||
Options.parse(['--foo'], context: context);
|
||||
fail('Expected exception');
|
||||
} on TestExit catch (e) {
|
||||
expect(e.code, 15, reason: 'exit code');
|
||||
}
|
||||
});
|
||||
|
||||
test('invalid target', () {
|
||||
parse(['foo.dart'],
|
||||
errorOut: 'Expected directory, but found', exitCode: 15);
|
||||
|
@ -96,7 +104,7 @@ main() {
|
|||
});
|
||||
|
||||
test('verbose', () {
|
||||
parse(['--verbose', 'foo'], verbose: true, normalOut: 'Targets:');
|
||||
parse(['--verbose', 'foo'], verbose: true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -115,34 +123,3 @@ void expectContains(Iterable<String> collection, String suffix) {
|
|||
}
|
||||
fail('Expected one of $collection\n to end with "$suffix"');
|
||||
}
|
||||
|
||||
class _TestLogger implements Logger {
|
||||
final TestContext context;
|
||||
final Ansi ansi;
|
||||
|
||||
_TestLogger(this.context) : this.ansi = new Ansi(false);
|
||||
|
||||
@override
|
||||
void flush() {}
|
||||
|
||||
@override
|
||||
bool get isVerbose => false;
|
||||
|
||||
@override
|
||||
Progress progress(String message) {
|
||||
return new SimpleProgress(this, message);
|
||||
}
|
||||
|
||||
@override
|
||||
void stderr(String message) {
|
||||
context.stderr.writeln(message);
|
||||
}
|
||||
|
||||
@override
|
||||
void stdout(String message) {
|
||||
context.stdout.writeln(message);
|
||||
}
|
||||
|
||||
@override
|
||||
void trace(String message) {}
|
||||
}
|
||||
|
|
|
@ -2,21 +2,11 @@
|
|||
// 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 'dart:async';
|
||||
|
||||
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
|
||||
import 'package:analyzer_cli/src/fix/context.dart';
|
||||
import 'package:cli_util/cli_logging.dart';
|
||||
|
||||
class TestContext with ResourceProviderMixin implements Context {
|
||||
final StreamController<List<int>> stdinController =
|
||||
new StreamController<List<int>>();
|
||||
|
||||
@override
|
||||
final stdout = new StringBuffer();
|
||||
|
||||
@override
|
||||
final stderr = new StringBuffer();
|
||||
|
||||
@override
|
||||
String get workingDir => convertPath('/usr/some/non/existing/directory');
|
||||
|
||||
|
@ -30,10 +20,6 @@ class TestContext with ResourceProviderMixin implements Context {
|
|||
|
||||
@override
|
||||
bool isDirectory(String filePath) => !filePath.endsWith('.dart');
|
||||
|
||||
void print([String text = '']) {
|
||||
stdout.writeln(text);
|
||||
}
|
||||
}
|
||||
|
||||
class TestExit {
|
||||
|
@ -41,3 +27,35 @@ class TestExit {
|
|||
|
||||
TestExit(this.code);
|
||||
}
|
||||
|
||||
class TestLogger implements Logger {
|
||||
final Ansi ansi;
|
||||
final stdoutBuffer = new StringBuffer();
|
||||
final stderrBuffer = new StringBuffer();
|
||||
|
||||
TestLogger() : this.ansi = new Ansi(false);
|
||||
|
||||
@override
|
||||
void flush() {}
|
||||
|
||||
@override
|
||||
bool get isVerbose => false;
|
||||
|
||||
@override
|
||||
Progress progress(String message) {
|
||||
return new SimpleProgress(this, message);
|
||||
}
|
||||
|
||||
@override
|
||||
void stderr(String message) {
|
||||
stderrBuffer.writeln(message);
|
||||
}
|
||||
|
||||
@override
|
||||
void stdout(String message) {
|
||||
stdoutBuffer.writeln(message);
|
||||
}
|
||||
|
||||
@override
|
||||
void trace(String message) {}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue