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