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 | | }
|