@@ -5,10 +5,15 @@ pub use self::json::*;
55pub use self :: modified_lines:: * ;
66pub use self :: stdout:: * ;
77
8+ use std:: fs;
89use std:: io:: { self , Write } ;
910use std:: path:: Path ;
11+ use std:: rc:: Rc ;
1012
11- use crate :: config:: FileName ;
13+ use thiserror:: Error ;
14+
15+ use crate :: syntux:: session:: ParseSess ;
16+ use crate :: { config:: FileName , FormatReport , FormatResult , NewlineStyle } ;
1217
1318pub mod checkstyle;
1419pub mod diff;
@@ -29,25 +34,200 @@ pub struct EmitterResult {
2934 pub has_diff : bool ,
3035}
3136
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+
3247pub trait Emitter {
3348 fn emit_formatted_file (
3449 & mut self ,
3550 output : & mut dyn Write ,
3651 formatted_file : FormattedFile < ' _ > ,
37- ) -> Result < EmitterResult , io :: Error > ;
52+ ) -> Result < EmitterResult , EmitterError > ;
3853
39- fn emit_header ( & self , _output : & mut dyn Write ) -> Result < ( ) , io :: Error > {
54+ fn emit_header ( & self , _output : & mut dyn Write ) -> Result < ( ) , EmitterError > {
4055 Ok ( ( ) )
4156 }
4257
43- fn emit_footer ( & self , _output : & mut dyn Write ) -> Result < ( ) , io :: Error > {
58+ fn emit_footer ( & self , _output : & mut dyn Write ) -> Result < ( ) , EmitterError > {
4459 Ok ( ( ) )
4560 }
4661}
4762
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) ) ,
52232 }
53233}
0 commit comments