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