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