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