| 1 | | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
|
| 2 | | // for details. All rights reserved. Use of this source code is governed by a
|
| 3 | | // BSD-style license that can be found in the LICENSE file.
|
| 4 | |
|
| 5 | | library dart_style.src.formatter_options;
|
| 6 | |
|
| 7 | | import 'dart:convert';
|
| 8 | | import 'dart:io';
|
| 9 | |
|
| 10 | | import 'source_code.dart';
|
| 11 | | import 'style_fix.dart';
|
| 12 | |
|
| 13 | | /// Global options that affect how the formatter produces and uses its outputs.
|
| 14 | | class FormatterOptions {
|
| 15 | | /// The [OutputReporter] used to show the formatting results.
|
| 16 | | final OutputReporter reporter;
|
| 17 | |
|
| 18 | | /// The number of spaces of indentation to prefix the output with.
|
| 19 | | final int indent;
|
| 20 | |
|
| 21 | | /// The number of columns that formatted output should be constrained to fit
|
| 22 | | /// within.
|
| 23 | | final int pageWidth;
|
| 24 | |
|
| 25 | | /// Whether symlinks should be traversed when formatting a directory.
|
| 26 | | final bool followLinks;
|
| 27 | |
|
| 28 | | /// The style fixes to apply while formatting.
|
| 29 | | final Iterable<StyleFix> fixes;
|
| 30 | |
|
| 31 | 1 | FormatterOptions(this.reporter,
|
| 32 | | {this.indent: 0,
|
| 33 | | this.pageWidth: 80,
|
| 34 | | this.followLinks: false,
|
| 35 | | this.fixes});
|
| 36 | | }
|
| 37 | |
|
| 38 | | /// How the formatter reports the results it produces.
|
| 39 | | abstract class OutputReporter {
|
| 40 | | /// Prints only the names of files whose contents are different from their
|
| 41 | | /// formatted version.
|
| 42 | | static final OutputReporter dryRun = new _DryRunReporter();
|
| 43 | |
|
| 44 | | /// Prints the formatted results of each file to stdout.
|
| 45 | | static final OutputReporter print = new _PrintReporter();
|
| 46 | |
|
| 47 | | /// Prints the formatted result and selection info of each file to stdout as
|
| 48 | | /// a JSON map.
|
| 49 | | static final OutputReporter printJson = new _PrintJsonReporter();
|
| 50 | |
|
| 51 | | /// Overwrites each file with its formatted result.
|
| 52 | | static final OutputReporter overwrite = new _OverwriteReporter();
|
| 53 | |
|
| 54 | | /// Describe the directory whose contents are about to be processed.
|
| 55 | 0 | void showDirectory(String path) {}
|
| 56 | |
|
| 57 | | /// Describe the symlink at [path] that wasn't followed.
|
| 58 | 0 | void showSkippedLink(String path) {}
|
| 59 | |
|
| 60 | | /// Describe the hidden [path] that wasn't processed.
|
| 61 | 0 | void showHiddenPath(String path) {}
|
| 62 | |
|
| 63 | | /// Called when [file] is about to be formatted.
|
| 64 | 1 | void beforeFile(File file, String label) {}
|
| 65 | |
|
| 66 | | /// Describe the processed file at [path] whose formatted result is [output].
|
| 67 | | ///
|
| 68 | | /// If the contents of the file are the same as the formatted output,
|
| 69 | | /// [changed] will be false.
|
| 70 | | void afterFile(File file, String label, SourceCode output, {bool changed});
|
| 71 | | }
|
| 72 | |
|
| 73 | | /// Prints only the names of files whose contents are different from their
|
| 74 | | /// formatted version.
|
| 75 | | class _DryRunReporter extends OutputReporter {
|
| 76 | 0 | void afterFile(File file, String label, SourceCode output, {bool changed}) {
|
| 77 | | // Only show the changed files.
|
| 78 | 0 | if (changed) print(label);
|
| 79 | | }
|
| 80 | | }
|
| 81 | |
|
| 82 | | /// Prints the formatted results of each file to stdout.
|
| 83 | | class _PrintReporter extends OutputReporter {
|
| 84 | 1 | void showDirectory(String path) {
|
| 85 | 2 | print("Formatting directory $path:");
|
| 86 | | }
|
| 87 | |
|
| 88 | 1 | void showSkippedLink(String path) {
|
| 89 | 2 | print("Skipping link $path");
|
| 90 | | }
|
| 91 | |
|
| 92 | 1 | void showHiddenPath(String path) {
|
| 93 | 2 | print("Skipping hidden path $path");
|
| 94 | | }
|
| 95 | |
|
| 96 | 0 | void afterFile(File file, String label, SourceCode output, {bool changed}) {
|
| 97 | | // Don't add an extra newline.
|
| 98 | 0 | stdout.write(output.text);
|
| 99 | | }
|
| 100 | | }
|
| 101 | |
|
| 102 | | /// Prints the formatted result and selection info of each file to stdout as a
|
| 103 | | /// JSON map.
|
| 104 | | class _PrintJsonReporter extends OutputReporter {
|
| 105 | 0 | void afterFile(File file, String label, SourceCode output, {bool changed}) {
|
| 106 | | // TODO(rnystrom): Put an empty selection in here to remain compatible with
|
| 107 | | // the old formatter. Since there's no way to pass a selection on the
|
| 108 | | // command line, this will never be used, which is why it's hard-coded to
|
| 109 | | // -1, -1. If we add support for passing in a selection, put the real
|
| 110 | | // result here.
|
| 111 | 0 | print(jsonEncode({
|
| 112 | | "path": label,
|
| 113 | 0 | "source": output.text,
|
| 114 | 0 | "selection": {
|
| 115 | 0 | "offset": output.selectionStart != null ? output.selectionStart : -1,
|
| 116 | 0 | "length": output.selectionLength != null ? output.selectionLength : -1
|
| 117 | | }
|
| 118 | | }));
|
| 119 | | }
|
| 120 | | }
|
| 121 | |
|
| 122 | | /// Overwrites each file with its formatted result.
|
| 123 | | class _OverwriteReporter extends _PrintReporter {
|
| 124 | 1 | void afterFile(File file, String label, SourceCode output, {bool changed}) {
|
| 125 | | if (changed) {
|
| 126 | | try {
|
| 127 | 2 | file.writeAsStringSync(output.text);
|
| 128 | 2 | print("Formatted $label");
|
| 129 | 1 | } on FileSystemException catch (err) {
|
| 130 | 2 | stderr.writeln("Could not overwrite $label: "
|
| 131 | 4 | "${err.osError.message} (error code ${err.osError.errorCode})");
|
| 132 | | }
|
| 133 | | } else {
|
| 134 | 2 | print("Unchanged $label");
|
| 135 | | }
|
| 136 | | }
|
| 137 | | }
|
| 138 | |
|
| 139 | | /// Base clase for a reporter that decorates an inner reporter.
|
| 140 | | abstract class _ReporterDecorator implements OutputReporter {
|
| 141 | | final OutputReporter _inner;
|
| 142 | |
|
| 143 | 0 | _ReporterDecorator(this._inner);
|
| 144 | |
|
| 145 | 0 | void showDirectory(String path) {
|
| 146 | 0 | _inner.showDirectory(path);
|
| 147 | | }
|
| 148 | |
|
| 149 | 0 | void showSkippedLink(String path) {
|
| 150 | 0 | _inner.showSkippedLink(path);
|
| 151 | | }
|
| 152 | |
|
| 153 | 0 | void showHiddenPath(String path) {
|
| 154 | 0 | _inner.showHiddenPath(path);
|
| 155 | | }
|
| 156 | |
|
| 157 | 0 | void beforeFile(File file, String label) {
|
| 158 | 0 | _inner.beforeFile(file, label);
|
| 159 | | }
|
| 160 | |
|
| 161 | 0 | void afterFile(File file, String label, SourceCode output, {bool changed}) {
|
| 162 | 0 | _inner.afterFile(file, label, output, changed: changed);
|
| 163 | | }
|
| 164 | | }
|
| 165 | |
|
| 166 | | /// A decorating reporter that reports how long it took for format each file.
|
| 167 | | class ProfileReporter extends _ReporterDecorator {
|
| 168 | | /// The files that have been started but have not completed yet.
|
| 169 | | ///
|
| 170 | | /// Maps a file label to the time that it started being formatted.
|
| 171 | | final Map<String, DateTime> _ongoing = {};
|
| 172 | |
|
| 173 | | /// The elapsed time it took to format each completed file.
|
| 174 | | final Map<String, Duration> _elapsed = {};
|
| 175 | |
|
| 176 | | /// The number of files that completed so fast that they aren't worth
|
| 177 | | /// tracking.
|
| 178 | | int _elided = 0;
|
| 179 | |
|
| 180 | 0 | ProfileReporter(OutputReporter inner) : super(inner);
|
| 181 | |
|
| 182 | | /// Show the times for the slowest files to format.
|
| 183 | 0 | void showProfile() {
|
| 184 | | // Everything should be done.
|
| 185 | 0 | assert(_ongoing.isEmpty);
|
| 186 | |
|
| 187 | 0 | var files = _elapsed.keys.toList();
|
| 188 | 0 | files.sort((a, b) => _elapsed[b].compareTo(_elapsed[a]));
|
| 189 | |
|
| 190 | 0 | for (var file in files) {
|
| 191 | 0 | print("${_elapsed[file]}: $file");
|
| 192 | | }
|
| 193 | |
|
| 194 | 0 | if (_elided >= 1) {
|
| 195 | 0 | var s = _elided > 1 ? 's' : '';
|
| 196 | 0 | print("...$_elided more file$s each took less than 10ms.");
|
| 197 | | }
|
| 198 | | }
|
| 199 | |
|
| 200 | | /// Called when [file] is about to be formatted.
|
| 201 | 0 | void beforeFile(File file, String label) {
|
| 202 | 0 | super.beforeFile(file, label);
|
| 203 | 0 | _ongoing[label] = new DateTime.now();
|
| 204 | | }
|
| 205 | |
|
| 206 | | /// Describe the processed file at [path] whose formatted result is [output].
|
| 207 | | ///
|
| 208 | | /// If the contents of the file are the same as the formatted output,
|
| 209 | | /// [changed] will be false.
|
| 210 | 0 | void afterFile(File file, String label, SourceCode output, {bool changed}) {
|
| 211 | 0 | var elapsed = new DateTime.now().difference(_ongoing.remove(label));
|
| 212 | 0 | if (elapsed.inMilliseconds >= 10) {
|
| 213 | 0 | _elapsed[label] = elapsed;
|
| 214 | | } else {
|
| 215 | 0 | _elided++;
|
| 216 | | }
|
| 217 | |
|
| 218 | 0 | super.afterFile(file, label, output, changed: changed);
|
| 219 | | }
|
| 220 | | }
|
| 221 | |
|
| 222 | | /// A decorating reporter that sets the exit code to 1 if any changes are made.
|
| 223 | | class SetExitReporter extends _ReporterDecorator {
|
| 224 | 0 | SetExitReporter(OutputReporter inner) : super(inner);
|
| 225 | |
|
| 226 | | /// Describe the processed file at [path] whose formatted result is [output].
|
| 227 | | ///
|
| 228 | | /// If the contents of the file are the same as the formatted output,
|
| 229 | | /// [changed] will be false.
|
| 230 | 0 | void afterFile(File file, String label, SourceCode output, {bool changed}) {
|
| 231 | 0 | if (changed) exitCode = 1;
|
| 232 | |
|
| 233 | 0 | super.afterFile(file, label, output, changed: changed);
|
| 234 | | }
|
| 235 | | }
|