| // 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'; |
| import 'style_fix.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; |
| |
| /// The style fixes to apply while formatting. |
| final Iterable<StyleFix> fixes; |
| |
| FormatterOptions(this.reporter, |
| {this.indent: 0, |
| this.pageWidth: 80, |
| this.followLinks: false, |
| this.fixes}); |
| } |
| |
| /// 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); |
| } |
| } |