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