blob: 69e2200007a2b71dfe852062ca0b5b678d22962f [file] [log] [blame]
// Copyright (c) 2014, 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.
library dart_style.src.formatter_options;
import 'dart:convert';
import 'dart:io';
import 'source_code.dart';
/// Global options that affect how the formatter produces and uses its outputs.
class FormatterOptions {
/// The [OutputReporter] used to show the formatting results.
final OutputReporter reporter;
/// The number of spaces of indentation to prefix the output with.
final int indent;
/// The number of columns that formatted output should be constrained to fit
/// within.
final int pageWidth;
/// Whether symlinks should be traversed when formatting a directory.
final bool followLinks;
FormatterOptions(this.reporter,
{this.indent: 0, this.pageWidth: 80, this.followLinks: false});
}
/// How the formatter reports the results it produces.
abstract class OutputReporter {
/// Prints only the names of files whose contents are different from their
/// formatted version.
static final OutputReporter dryRun = new _DryRunReporter();
/// Prints the formatted results of each file to stdout.
static final OutputReporter print = new _PrintReporter();
/// Prints the formatted result and selection info of each file to stdout as
/// a JSON map.
static final OutputReporter printJson = new _PrintJsonReporter();
/// Overwrites each file with its formatted result.
static final OutputReporter overwrite = new _OverwriteReporter();
/// Describe the directory whose contents are about to be processed.
void showDirectory(String path) {}
/// Describe the symlink at [path] that wasn't followed.
void showSkippedLink(String path) {}
/// Describe the hidden [path] that wasn't processed.
void showHiddenPath(String path) {}
/// Called when [file] is about to be formatted.
void beforeFile(File file, String label) {}
/// Describe the processed file at [path] whose formatted result is [output].
///
/// If the contents of the file are the same as the formatted output,
/// [changed] will be false.
void afterFile(File file, String label, SourceCode output, {bool changed});
}
/// Prints only the names of files whose contents are different from their
/// formatted version.
class _DryRunReporter extends OutputReporter {
void afterFile(File file, String label, SourceCode output, {bool changed}) {
// Only show the changed files.
if (changed) print(label);
}
}
/// Prints the formatted results of each file to stdout.
class _PrintReporter extends OutputReporter {
void showDirectory(String path) {
print("Formatting directory $path:");
}
void showSkippedLink(String path) {
print("Skipping link $path");
}
void showHiddenPath(String path) {
print("Skipping hidden path $path");
}
void afterFile(File file, String label, SourceCode output, {bool changed}) {
// Don't add an extra newline.
stdout.write(output.text);
}
}
/// Prints the formatted result and selection info of each file to stdout as a
/// JSON map.
class _PrintJsonReporter extends OutputReporter {
void afterFile(File file, String label, SourceCode output, {bool changed}) {
// TODO(rnystrom): Put an empty selection in here to remain compatible with
// the old formatter. Since there's no way to pass a selection on the
// command line, this will never be used, which is why it's hard-coded to
// -1, -1. If we add support for passing in a selection, put the real
// result here.
print(jsonEncode({
"path": label,
"source": output.text,
"selection": {
"offset": output.selectionStart != null ? output.selectionStart : -1,
"length": output.selectionLength != null ? output.selectionLength : -1
}
}));
}
}
/// Overwrites each file with its formatted result.
class _OverwriteReporter extends _PrintReporter {
void afterFile(File file, String label, SourceCode output, {bool changed}) {
if (changed) {
try {
file.writeAsStringSync(output.text);
print("Formatted $label");
} on FileSystemException catch (err) {
stderr.writeln("Could not overwrite $label: "
"${err.osError.message} (error code ${err.osError.errorCode})");
}
} else {
print("Unchanged $label");
}
}
}
/// Base clase for a reporter that decorates an inner reporter.
abstract class _ReporterDecorator implements OutputReporter {
final OutputReporter _inner;
_ReporterDecorator(this._inner);
void showDirectory(String path) {
_inner.showDirectory(path);
}
void showSkippedLink(String path) {
_inner.showSkippedLink(path);
}
void showHiddenPath(String path) {
_inner.showHiddenPath(path);
}
void beforeFile(File file, String label) {
_inner.beforeFile(file, label);
}
void afterFile(File file, String label, SourceCode output, {bool changed}) {
_inner.afterFile(file, label, output, changed: changed);
}
}
/// A decorating reporter that reports how long it took for format each file.
class ProfileReporter extends _ReporterDecorator {
/// The files that have been started but have not completed yet.
///
/// Maps a file label to the time that it started being formatted.
final Map<String, DateTime> _ongoing = {};
/// The elapsed time it took to format each completed file.
final Map<String, Duration> _elapsed = {};
/// The number of files that completed so fast that they aren't worth
/// tracking.
int _elided = 0;
ProfileReporter(OutputReporter inner) : super(inner);
/// Show the times for the slowest files to format.
void showProfile() {
// Everything should be done.
assert(_ongoing.isEmpty);
var files = _elapsed.keys.toList();
files.sort((a, b) => _elapsed[b].compareTo(_elapsed[a]));
for (var file in files) {
print("${_elapsed[file]}: $file");
}
if (_elided >= 1) {
var s = _elided > 1 ? 's' : '';
print("...$_elided more file$s each took less than 10ms.");
}
}
/// Called when [file] is about to be formatted.
void beforeFile(File file, String label) {
super.beforeFile(file, label);
_ongoing[label] = new DateTime.now();
}
/// Describe the processed file at [path] whose formatted result is [output].
///
/// If the contents of the file are the same as the formatted output,
/// [changed] will be false.
void afterFile(File file, String label, SourceCode output, {bool changed}) {
var elapsed = new DateTime.now().difference(_ongoing.remove(label));
if (elapsed.inMilliseconds >= 10) {
_elapsed[label] = elapsed;
} else {
_elided++;
}
super.afterFile(file, label, output, changed: changed);
}
}
/// A decorating reporter that sets the exit code to 1 if any changes are made.
class SetExitReporter extends _ReporterDecorator {
SetExitReporter(OutputReporter inner) : super(inner);
/// Describe the processed file at [path] whose formatted result is [output].
///
/// If the contents of the file are the same as the formatted output,
/// [changed] will be false.
void afterFile(File file, String label, SourceCode output, {bool changed}) {
if (changed) exitCode = 1;
super.afterFile(file, label, output, changed: changed);
}
}