]> git.lizzy.rs Git - rust.git/blob - src/config/options.rs
Make some write modes unstable
[rust.git] / src / config / options.rs
1 // Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 use syntax::codemap::FileName;
12
13 use config::config_type::ConfigType;
14 use config::file_lines::FileLines;
15 use config::lists::*;
16 use config::Config;
17 use FmtResult;
18
19 use failure::err_msg;
20
21 use getopts::Matches;
22 use std::collections::HashSet;
23 use std::path::{Path, PathBuf};
24 use std::str::FromStr;
25
26 /// Macro for deriving implementations of Serialize/Deserialize for enums
27 #[macro_export]
28 macro_rules! impl_enum_serialize_and_deserialize {
29     ( $e:ident, $( $x:ident ),* ) => {
30         impl ::serde::ser::Serialize for $e {
31             fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
32                 where S: ::serde::ser::Serializer
33             {
34                 use serde::ser::Error;
35
36                 // We don't know whether the user of the macro has given us all options.
37                 #[allow(unreachable_patterns)]
38                 match *self {
39                     $(
40                         $e::$x => serializer.serialize_str(stringify!($x)),
41                     )*
42                     _ => {
43                         Err(S::Error::custom(format!("Cannot serialize {:?}", self)))
44                     }
45                 }
46             }
47         }
48
49         impl<'de> ::serde::de::Deserialize<'de> for $e {
50             fn deserialize<D>(d: D) -> Result<Self, D::Error>
51                     where D: ::serde::Deserializer<'de> {
52                 use serde::de::{Error, Visitor};
53                 use std::marker::PhantomData;
54                 use std::fmt;
55                 struct StringOnly<T>(PhantomData<T>);
56                 impl<'de, T> Visitor<'de> for StringOnly<T>
57                         where T: ::serde::Deserializer<'de> {
58                     type Value = String;
59                     fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
60                         formatter.write_str("string")
61                     }
62                     fn visit_str<E>(self, value: &str) -> Result<String, E> {
63                         Ok(String::from(value))
64                     }
65                 }
66                 let s = d.deserialize_string(StringOnly::<D>(PhantomData))?;
67                 $(
68                     if stringify!($x).eq_ignore_ascii_case(&s) {
69                       return Ok($e::$x);
70                     }
71                 )*
72                 static ALLOWED: &'static[&str] = &[$(stringify!($x),)*];
73                 Err(D::Error::unknown_variant(&s, ALLOWED))
74             }
75         }
76
77         impl ::std::str::FromStr for $e {
78             type Err = &'static str;
79
80             fn from_str(s: &str) -> Result<Self, Self::Err> {
81                 $(
82                     if stringify!($x).eq_ignore_ascii_case(s) {
83                         return Ok($e::$x);
84                     }
85                 )*
86                 Err("Bad variant")
87             }
88         }
89
90         impl ConfigType for $e {
91             fn doc_hint() -> String {
92                 let mut variants = Vec::new();
93                 $(
94                     variants.push(stringify!($x));
95                 )*
96                 format!("[{}]", variants.join("|"))
97             }
98         }
99     };
100 }
101
102 macro_rules! configuration_option_enum{
103     ($e:ident: $( $x:ident ),+ $(,)*) => {
104         #[derive(Copy, Clone, Eq, PartialEq, Debug)]
105         pub enum $e {
106             $( $x ),+
107         }
108
109         impl_enum_serialize_and_deserialize!($e, $( $x ),+);
110     }
111 }
112
113 configuration_option_enum! { NewlineStyle:
114     Windows, // \r\n
115     Unix, // \n
116     Native, // \r\n in Windows, \n on other platforms
117 }
118
119 configuration_option_enum! { BraceStyle:
120     AlwaysNextLine,
121     PreferSameLine,
122     // Prefer same line except where there is a where clause, in which case force
123     // the brace to the next line.
124     SameLineWhere,
125 }
126
127 configuration_option_enum! { ControlBraceStyle:
128     // K&R style, Rust community default
129     AlwaysSameLine,
130     // Stroustrup style
131     ClosingNextLine,
132     // Allman style
133     AlwaysNextLine,
134 }
135
136 configuration_option_enum! { IndentStyle:
137     // First line on the same line as the opening brace, all lines aligned with
138     // the first line.
139     Visual,
140     // First line is on a new line and all lines align with block indent.
141     Block,
142 }
143
144 configuration_option_enum! { Density:
145     // Fit as much on one line as possible.
146     Compressed,
147     // Use more lines.
148     Tall,
149     // Place every item on a separate line.
150     Vertical,
151 }
152
153 configuration_option_enum! { TypeDensity:
154     // No spaces around "=" and "+"
155     Compressed,
156     // Spaces around " = " and " + "
157     Wide,
158 }
159
160 impl Density {
161     pub fn to_list_tactic(self) -> ListTactic {
162         match self {
163             Density::Compressed => ListTactic::Mixed,
164             Density::Tall => ListTactic::HorizontalVertical,
165             Density::Vertical => ListTactic::Vertical,
166         }
167     }
168 }
169
170 configuration_option_enum! { ReportTactic:
171     Always,
172     Unnumbered,
173     Never,
174 }
175
176 configuration_option_enum! { WriteMode:
177     // Overwrites original file without backup.
178     Overwrite,
179     // Backs the original file up and overwrites the original.
180     Replace,
181     // Writes the output to stdout.
182     Display,
183     // Displays how much of the input file was processed
184     Coverage,
185     // Unfancy stdout
186     Checkstyle,
187     // Output the changed lines (for internal value only)
188     Modified,
189     // Checks if a diff can be generated. If so, rustfmt outputs a diff and quits with exit code 1.
190     // This option is designed to be run in CI where a non-zero exit signifies non-standard code
191     // formatting.
192     Check,
193     // Rustfmt shouldn't output anything formatting-like (e.g., emit a help message).
194     None,
195 }
196
197 const STABLE_WRITE_MODES: [WriteMode; 4] = [
198     WriteMode::Replace,
199     WriteMode::Overwrite,
200     WriteMode::Display,
201     WriteMode::Check,
202 ];
203
204 configuration_option_enum! { Color:
205     // Always use color, whether it is a piped or terminal output
206     Always,
207     // Never use color
208     Never,
209     // Automatically use color, if supported by terminal
210     Auto,
211 }
212
213 configuration_option_enum! { Verbosity:
214     // Emit more.
215     Verbose,
216     Normal,
217     // Emit as little as possible.
218     Quiet,
219 }
220
221 #[derive(Deserialize, Serialize, Clone, Debug)]
222 pub struct WidthHeuristics {
223     // Maximum width of the args of a function call before falling back
224     // to vertical formatting.
225     pub fn_call_width: usize,
226     // Maximum width in the body of a struct lit before falling back to
227     // vertical formatting.
228     pub struct_lit_width: usize,
229     // Maximum width in the body of a struct variant before falling back
230     // to vertical formatting.
231     pub struct_variant_width: usize,
232     // Maximum width of an array literal before falling back to vertical
233     // formatting.
234     pub array_width: usize,
235     // Maximum length of a chain to fit on a single line.
236     pub chain_width: usize,
237     // Maximum line length for single line if-else expressions. A value
238     // of zero means always break if-else expressions.
239     pub single_line_if_else_max_width: usize,
240 }
241
242 impl WidthHeuristics {
243     // Using this WidthHeuristics means we ignore heuristics.
244     pub fn null() -> WidthHeuristics {
245         WidthHeuristics {
246             fn_call_width: usize::max_value(),
247             struct_lit_width: 0,
248             struct_variant_width: 0,
249             array_width: usize::max_value(),
250             chain_width: usize::max_value(),
251             single_line_if_else_max_width: 0,
252         }
253     }
254
255     // scale the default WidthHeuristics according to max_width
256     pub fn scaled(max_width: usize) -> WidthHeuristics {
257         const DEFAULT_MAX_WIDTH: usize = 100;
258         let max_width_ratio = if max_width > DEFAULT_MAX_WIDTH {
259             let ratio = max_width as f32 / DEFAULT_MAX_WIDTH as f32;
260             // round to the closest 0.1
261             (ratio * 10.0).round() / 10.0
262         } else {
263             1.0
264         };
265         WidthHeuristics {
266             fn_call_width: (60.0 * max_width_ratio).round() as usize,
267             struct_lit_width: (18.0 * max_width_ratio).round() as usize,
268             struct_variant_width: (35.0 * max_width_ratio).round() as usize,
269             array_width: (60.0 * max_width_ratio).round() as usize,
270             chain_width: (60.0 * max_width_ratio).round() as usize,
271             single_line_if_else_max_width: (50.0 * max_width_ratio).round() as usize,
272         }
273     }
274 }
275
276 impl ::std::str::FromStr for WidthHeuristics {
277     type Err = &'static str;
278
279     fn from_str(_: &str) -> Result<Self, Self::Err> {
280         Err("WidthHeuristics is not parsable")
281     }
282 }
283
284 impl Default for WriteMode {
285     fn default() -> WriteMode {
286         WriteMode::Overwrite
287     }
288 }
289
290 /// A set of directories, files and modules that rustfmt should ignore.
291 #[derive(Default, Deserialize, Serialize, Clone, Debug)]
292 pub struct IgnoreList(HashSet<PathBuf>);
293
294 impl IgnoreList {
295     pub fn add_prefix(&mut self, dir: &Path) {
296         self.0 = self
297             .0
298             .iter()
299             .map(|s| {
300                 if s.has_root() {
301                     s.clone()
302                 } else {
303                     let mut path = PathBuf::from(dir);
304                     path.push(s);
305                     path
306                 }
307             })
308             .collect();
309     }
310
311     fn skip_file_inner(&self, file: &Path) -> bool {
312         self.0.iter().any(|path| file.starts_with(path))
313     }
314
315     pub fn skip_file(&self, file: &FileName) -> bool {
316         if let FileName::Real(ref path) = file {
317             self.skip_file_inner(path)
318         } else {
319             false
320         }
321     }
322 }
323
324 impl ::std::str::FromStr for IgnoreList {
325     type Err = &'static str;
326
327     fn from_str(_: &str) -> Result<Self, Self::Err> {
328         Err("IgnoreList is not parsable")
329     }
330 }
331
332 /// Parsed command line options.
333 #[derive(Clone, Debug, Default)]
334 pub struct CliOptions {
335     pub skip_children: Option<bool>,
336     pub quiet: bool,
337     pub verbose: bool,
338     pub config_path: Option<PathBuf>,
339     pub write_mode: WriteMode,
340     pub check: bool,
341     pub color: Option<Color>,
342     pub file_lines: FileLines, // Default is all lines in all files.
343     pub unstable_features: bool,
344     pub error_on_unformatted: Option<bool>,
345 }
346
347 impl CliOptions {
348     pub fn from_matches(matches: &Matches) -> FmtResult<CliOptions> {
349         let mut options = CliOptions::default();
350         options.verbose = matches.opt_present("verbose");
351         options.quiet = matches.opt_present("quiet");
352         if options.verbose && options.quiet {
353             return Err(format_err!("Can't use both `--verbose` and `--quiet`"));
354         }
355
356         let rust_nightly = option_env!("CFG_RELEASE_CHANNEL")
357             .map(|c| c == "nightly")
358             .unwrap_or(false);
359         if rust_nightly {
360             options.unstable_features = matches.opt_present("unstable-features");
361         }
362
363         if !options.unstable_features {
364             if matches.opt_present("skip-children") {
365                 options.skip_children = Some(true);
366             }
367             if matches.opt_present("error-on-unformatted") {
368                 options.error_on_unformatted = Some(true);
369             }
370             if let Some(ref file_lines) = matches.opt_str("file-lines") {
371                 options.file_lines = file_lines.parse().map_err(err_msg)?;
372             }
373         }
374
375         options.config_path = matches.opt_str("config-path").map(PathBuf::from);
376
377         options.check = matches.opt_present("check");
378         if let Some(ref emit_str) = matches.opt_str("emit") {
379             if options.check {
380                 return Err(format_err!("Invalid to use `--emit` and `--check`"));
381             }
382             if let Ok(write_mode) = write_mode_from_emit_str(emit_str) {
383                 options.write_mode = write_mode;
384             } else {
385                 return Err(format_err!("Invalid value for `--emit`"));
386             }
387         }
388
389         if options.write_mode == WriteMode::Overwrite && matches.opt_present("backup") {
390             options.write_mode = WriteMode::Replace;
391         }
392
393         if !rust_nightly {
394             if !STABLE_WRITE_MODES.contains(&options.write_mode) {
395                 return Err(format_err!(
396                     "Invalid value for `--emit` - using an unstable \
397                      value without `--unstable-features`",
398                 ));
399             }
400         }
401
402         if let Some(ref color) = matches.opt_str("color") {
403             match Color::from_str(color) {
404                 Ok(color) => options.color = Some(color),
405                 _ => return Err(format_err!("Invalid color: {}", color)),
406             }
407         }
408
409         Ok(options)
410     }
411
412     pub fn apply_to(self, config: &mut Config) {
413         if self.verbose {
414             config.set().verbose(Verbosity::Verbose);
415         } else if self.quiet {
416             config.set().verbose(Verbosity::Quiet);
417         } else {
418             config.set().verbose(Verbosity::Normal);
419         }
420         config.set().file_lines(self.file_lines);
421         config.set().unstable_features(self.unstable_features);
422         if let Some(skip_children) = self.skip_children {
423             config.set().skip_children(skip_children);
424         }
425         if let Some(error_on_unformatted) = self.error_on_unformatted {
426             config.set().error_on_unformatted(error_on_unformatted);
427         }
428         if self.check {
429             config.set().write_mode(WriteMode::Check);
430         } else {
431             config.set().write_mode(self.write_mode);
432         }
433         if let Some(color) = self.color {
434             config.set().color(color);
435         }
436     }
437
438     pub fn verify_file_lines(&self, files: &[PathBuf]) {
439         for f in self.file_lines.files() {
440             match *f {
441                 FileName::Real(ref f) if files.contains(f) => {}
442                 FileName::Real(_) => {
443                     eprintln!("Warning: Extra file listed in file_lines option '{}'", f)
444                 }
445                 _ => eprintln!("Warning: Not a file '{}'", f),
446             }
447         }
448     }
449 }
450
451 fn write_mode_from_emit_str(emit_str: &str) -> FmtResult<WriteMode> {
452     match emit_str {
453         "files" => Ok(WriteMode::Overwrite),
454         "stdout" => Ok(WriteMode::Display),
455         "coverage" => Ok(WriteMode::Coverage),
456         "checkstyle" => Ok(WriteMode::Checkstyle),
457         _ => Err(format_err!("Invalid value for `--emit`")),
458     }
459 }