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