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