@@ -5,10 +5,15 @@ pub use self::json::*;
5
5
pub use self :: modified_lines:: * ;
6
6
pub use self :: stdout:: * ;
7
7
8
+ use std:: fs;
8
9
use std:: io:: { self , Write } ;
9
10
use std:: path:: Path ;
11
+ use std:: rc:: Rc ;
10
12
11
- use crate :: config:: FileName ;
13
+ use thiserror:: Error ;
14
+
15
+ use crate :: syntux:: session:: ParseSess ;
16
+ use crate :: { config:: FileName , FormatReport , FormatResult , NewlineStyle } ;
12
17
13
18
pub mod checkstyle;
14
19
pub mod diff;
@@ -29,25 +34,200 @@ pub struct EmitterResult {
29
34
pub has_diff : bool ,
30
35
}
31
36
37
+ #[ derive( Debug , Error ) ]
38
+ pub enum EmitterError {
39
+ #[ error( "{0}" ) ]
40
+ IoError ( #[ from] io:: Error ) ,
41
+ #[ error( "{0}" ) ]
42
+ JsonError ( #[ from] serde_json:: Error ) ,
43
+ #[ error( "invalid input for EmitMode::Files" ) ]
44
+ InvalidInputForFiles ,
45
+ }
46
+
32
47
pub trait Emitter {
33
48
fn emit_formatted_file (
34
49
& mut self ,
35
50
output : & mut dyn Write ,
36
51
formatted_file : FormattedFile < ' _ > ,
37
- ) -> Result < EmitterResult , io :: Error > ;
52
+ ) -> Result < EmitterResult , EmitterError > ;
38
53
39
- fn emit_header ( & self , _output : & mut dyn Write ) -> Result < ( ) , io :: Error > {
54
+ fn emit_header ( & self , _output : & mut dyn Write ) -> Result < ( ) , EmitterError > {
40
55
Ok ( ( ) )
41
56
}
42
57
43
- fn emit_footer ( & self , _output : & mut dyn Write ) -> Result < ( ) , io :: Error > {
58
+ fn emit_footer ( & self , _output : & mut dyn Write ) -> Result < ( ) , EmitterError > {
44
59
Ok ( ( ) )
45
60
}
46
61
}
47
62
48
- fn ensure_real_path ( filename : & FileName ) -> & Path {
49
- match * filename {
50
- FileName :: Real ( ref path) => path,
51
- _ => panic ! ( "cannot format `{}` and emit to files" , filename) ,
63
+ /// What Rustfmt should emit. Mostly corresponds to the `--emit` command line
64
+ /// option.
65
+ #[ derive( Clone , Copy , Debug ) ]
66
+ pub enum EmitMode {
67
+ /// Emits to files.
68
+ Files ,
69
+ /// Writes the output to stdout.
70
+ Stdout ,
71
+ /// Unfancy stdout
72
+ Checkstyle ,
73
+ /// Writes the resulting diffs in a JSON format. Returns an empty array
74
+ /// `[]` if there were no diffs.
75
+ Json ,
76
+ /// Output the changed lines (for internal value only)
77
+ ModifiedLines ,
78
+ /// Checks if a diff can be generated. If so, rustfmt outputs a diff and
79
+ /// quits with exit code 1.
80
+ /// This option is designed to be run in CI where a non-zero exit signifies
81
+ /// non-standard code formatting. Used for `--check`.
82
+ Diff ,
83
+ }
84
+
85
+ /// Client-preference for coloured output.
86
+ #[ derive( Debug , Clone , Copy , PartialEq ) ]
87
+ pub enum Color {
88
+ /// Always use color, whether it is a piped or terminal output
89
+ Always ,
90
+ /// Never use color
91
+ Never ,
92
+ /// Automatically use color, if supported by terminal
93
+ Auto ,
94
+ }
95
+
96
+ impl Color {
97
+ /// Whether we should use a coloured terminal.
98
+ pub fn use_colored_tty ( self ) -> bool {
99
+ match self {
100
+ Color :: Always | Color :: Auto => true ,
101
+ Color :: Never => false ,
102
+ }
103
+ }
104
+ }
105
+
106
+ /// How chatty should Rustfmt be?
107
+ #[ derive( Debug , Clone , Copy , PartialEq ) ]
108
+ pub enum Verbosity {
109
+ /// Default.
110
+ Normal ,
111
+ /// Emit more.
112
+ Verbose ,
113
+ /// Emit as little as possible.
114
+ Quiet ,
115
+ }
116
+
117
+ impl Default for Verbosity {
118
+ fn default ( ) -> Self {
119
+ Verbosity :: Normal
120
+ }
121
+ }
122
+
123
+ impl std:: str:: FromStr for EmitMode {
124
+ type Err = String ;
125
+
126
+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
127
+ match s {
128
+ "files" => Ok ( EmitMode :: Files ) ,
129
+ "stdout" => Ok ( EmitMode :: Stdout ) ,
130
+ "checkstyle" => Ok ( EmitMode :: Checkstyle ) ,
131
+ "json" => Ok ( EmitMode :: Json ) ,
132
+ _ => Err ( format ! ( "unknown emit mode `{}`" , s) ) ,
133
+ }
134
+ }
135
+ }
136
+
137
+ #[ derive( Clone , Copy , Debug ) ]
138
+ pub struct EmitterConfig {
139
+ pub emit_mode : EmitMode ,
140
+ pub color : Color ,
141
+ pub verbosity : Verbosity ,
142
+ pub print_filename : bool ,
143
+ }
144
+
145
+ impl Default for EmitterConfig {
146
+ fn default ( ) -> Self {
147
+ EmitterConfig {
148
+ emit_mode : EmitMode :: Files ,
149
+ color : Color :: Auto ,
150
+ verbosity : Verbosity :: Normal ,
151
+ print_filename : false ,
152
+ }
153
+ }
154
+ }
155
+
156
+ pub fn emit_format_report < T > (
157
+ format_report : FormatReport ,
158
+ out : & mut T ,
159
+ config : EmitterConfig ,
160
+ ) -> Result < bool , EmitterError >
161
+ where
162
+ T : Write ,
163
+ {
164
+ let mut emitter = create_emitter ( config) ;
165
+ let mut has_diff = false ;
166
+
167
+ emitter. emit_header ( out) ?;
168
+ for ( filename, format_result) in format_report. format_result . borrow ( ) . iter ( ) {
169
+ has_diff |= write_file ( None , filename, & format_result, out, & mut * emitter) ?. has_diff ;
170
+ }
171
+ emitter. emit_footer ( out) ?;
172
+
173
+ Ok ( has_diff)
174
+ }
175
+
176
+ pub ( crate ) fn write_file < T > (
177
+ parse_sess : Option < & ParseSess > ,
178
+ filename : & FileName ,
179
+ formatted_result : & FormatResult ,
180
+ out : & mut T ,
181
+ emitter : & mut dyn Emitter ,
182
+ ) -> Result < EmitterResult , EmitterError >
183
+ where
184
+ T : Write ,
185
+ {
186
+ fn ensure_real_path ( filename : & FileName ) -> & Path {
187
+ match * filename {
188
+ FileName :: Real ( ref path) => path,
189
+ _ => panic ! ( "cannot format `{}` and emit to files" , filename) ,
190
+ }
191
+ }
192
+
193
+ // SourceFile's in the SourceMap will always have Unix-style line endings
194
+ // See: https://github.com/rust-lang/rustfmt/issues/3850
195
+ // So if the user has explicitly overridden the rustfmt `newline_style`
196
+ // config and `filename` is FileName::Real, then we must check the file system
197
+ // to get the original file value in order to detect newline_style conflicts.
198
+ // Otherwise, parse session is around (cfg(not(test))) and newline_style has been
199
+ // left as the default value, then try getting source from the parse session
200
+ // source map instead of hitting the file system. This also supports getting
201
+ // original text for `FileName::Stdin`.
202
+ let original_text =
203
+ if formatted_result. newline_style != NewlineStyle :: Auto && * filename != FileName :: Stdin {
204
+ Rc :: new ( fs:: read_to_string ( ensure_real_path ( filename) ) ?)
205
+ } else {
206
+ match & formatted_result. original_snippet {
207
+ Some ( original_snippet) => Rc :: new ( original_snippet. to_owned ( ) ) ,
208
+ None => match parse_sess. and_then ( |sess| sess. get_original_snippet ( filename) ) {
209
+ Some ( ori) => ori,
210
+ None => Rc :: new ( fs:: read_to_string ( ensure_real_path ( filename) ) ?) ,
211
+ } ,
212
+ }
213
+ } ;
214
+
215
+ let formatted_file = FormattedFile {
216
+ filename,
217
+ original_text : original_text. as_str ( ) ,
218
+ formatted_text : & formatted_result. formatted_snippet . snippet ,
219
+ } ;
220
+
221
+ emitter. emit_formatted_file ( out, formatted_file)
222
+ }
223
+
224
+ fn create_emitter ( emitter_config : EmitterConfig ) -> Box < dyn Emitter > {
225
+ match emitter_config. emit_mode {
226
+ EmitMode :: Files => Box :: new ( FilesEmitter :: new ( emitter_config) ) ,
227
+ EmitMode :: Stdout => Box :: new ( StdoutEmitter :: new ( emitter_config) ) ,
228
+ EmitMode :: Json => Box :: new ( JsonEmitter :: default ( ) ) ,
229
+ EmitMode :: ModifiedLines => Box :: new ( ModifiedLinesEmitter :: default ( ) ) ,
230
+ EmitMode :: Checkstyle => Box :: new ( CheckstyleEmitter :: default ( ) ) ,
231
+ EmitMode :: Diff => Box :: new ( DiffEmitter :: new ( emitter_config) ) ,
52
232
}
53
233
}
0 commit comments