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