blob: 6e7fcee3a6976f5528056c287566f76f1b062d88 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. 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 'dart:convert';
import 'dart:io' as io;
import 'package:path/path.dart' as p;
import '../logger.dart';
import 'command.dart';
import 'flags.dart';
/// The 'format' command.
///
/// The format command implementation below works by spawning another Dart VM to
/// run the program under ci/bin/format.dart.
///
// TODO(team-engine): Part of https://github.com/flutter/flutter/issues/132807.
// Instead, format.dart should be moved under the engine_tool package and
// invoked by a function call. The file ci/bin/format.dart should be split up so
// that each of its `FormatCheckers` is in a separate file under src/formatters,
// and they should be unit-tested.
final class FormatCommand extends CommandBase {
// ignore: public_member_api_docs
FormatCommand({
required super.environment,
}) {
argParser
..addFlag(
allFlag,
abbr: 'a',
help: 'By default only dirty files are checked. This flag causes all '
'files to be checked. (Slow)',
negatable: false,
)
..addFlag(
dryRunFlag,
abbr: 'd',
help: 'Do not fix formatting in-place. Instead, print file diffs to '
'the logs.',
negatable: false,
)
..addFlag(
quietFlag,
abbr: 'q',
help: 'Silences all log messages except for errors and warnings',
negatable: false,
)
..addFlag(
verboseFlag,
abbr: 'v',
help: 'Prints verbose output',
negatable: false,
);
}
@override
String get name => 'format';
@override
String get description => 'Formats files using standard formatters and styles.';
@override
Future<int> run() async {
final bool all = argResults![allFlag]! as bool;
final bool dryRun = argResults![dryRunFlag]! as bool;
final bool quiet = argResults![quietFlag]! as bool;
final bool verbose = argResults![verboseFlag]! as bool;
final String formatPath = p.join(
environment.engine.flutterDir.path, 'ci', 'bin', 'format.dart',
);
final io.Process process = await environment.processRunner.processManager.start(
<String>[
environment.platform.resolvedExecutable,
formatPath,
if (all) '--all-files',
if (!dryRun) '--fix',
if (verbose) '--verbose',
],
workingDirectory: environment.engine.flutterDir.path,
);
final Completer<void> stdoutComplete = Completer<void>();
final Completer<void> stderrComplete = Completer<void>();
final _FormatStreamer streamer = _FormatStreamer(
environment.logger,
dryRun,
quiet,
);
process.stdout
.transform<String>(const Utf8Decoder())
.transform(const LineSplitter())
.listen(
streamer.nextStdout,
onDone: () async => stdoutComplete.complete(),
);
process.stderr
.transform<String>(const Utf8Decoder())
.transform(const LineSplitter())
.listen(
streamer.nextStderr,
onDone: () async => stderrComplete.complete(),
);
await Future.wait<void>(<Future<void>>[
stdoutComplete.future, stderrComplete.future,
]);
final int exitCode = await process.exitCode;
return exitCode;
}
}
class _FormatStreamer {
_FormatStreamer(this.logger, this.dryRun, this.quiet);
final Logger logger;
final bool dryRun;
final bool quiet;
bool inADiff = false;
bool inProgress = false;
void nextStdout(String line) {
if (quiet) {
return;
}
final String l = line.trim();
if (l == 'To fix, run `et format` or:') {
inADiff = true;
}
if (l.isNotEmpty && (!inADiff || dryRun)) {
if (_isProgressLine(l)) {
inProgress = true;
logger.clearLine();
logger.status('$l\r', newline: false);
} else {
if (inProgress) {
logger.clearLine();
inProgress = false;
}
logger.status(l);
}
}
if (l == 'DONE') {
inADiff = false;
}
}
void nextStderr(String line) {
final String l = line.trim();
if (l.isEmpty) {
return;
}
logger.error(l);
}
bool _isProgressLine(String l) {
final List<String> words = l.split(',');
return words.isNotEmpty && words[0].endsWith('% done');
}
}