]> git.lizzy.rs Git - rust.git/blob - src/config.rs
Merge pull request #1776 from topecongiro/poor-formatting/index-budget
[rust.git] / src / config.rs
1 // Copyright 2015 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 extern crate toml;
12
13 use std::cell::Cell;
14 use std::fs;
15 use std::fs::File;
16 use std::env;
17 use std::io::{Error, ErrorKind, Read};
18 use std::path::{Path, PathBuf};
19
20 use file_lines::FileLines;
21 use lists::{SeparatorTactic, ListTactic};
22
23 macro_rules! configuration_option_enum{
24     ($e:ident: $( $x:ident ),+ $(,)*) => {
25         #[derive(Copy, Clone, Eq, PartialEq, Debug)]
26         pub enum $e {
27             $( $x ),+
28         }
29
30         impl_enum_serialize_and_deserialize!($e, $( $x ),+);
31     }
32 }
33
34 configuration_option_enum! { Style:
35     Rfc, // Follow the style RFCs style.
36     Legacy, // Follow the traditional Rustfmt style.
37 }
38
39 configuration_option_enum! { NewlineStyle:
40     Windows, // \r\n
41     Unix, // \n
42     Native, // \r\n in Windows, \n on other platforms
43 }
44
45 configuration_option_enum! { BraceStyle:
46     AlwaysNextLine,
47     PreferSameLine,
48     // Prefer same line except where there is a where clause, in which case force
49     // the brace to the next line.
50     SameLineWhere,
51 }
52
53 configuration_option_enum! { ControlBraceStyle:
54     // K&R style, Rust community default
55     AlwaysSameLine,
56     // Stroustrup style
57     ClosingNextLine,
58     // Allman style
59     AlwaysNextLine,
60 }
61
62 // How to indent a function's return type.
63 configuration_option_enum! { ReturnIndent:
64     // Aligned with the arguments
65     WithArgs,
66     // Aligned with the where clause
67     WithWhereClause,
68 }
69
70 configuration_option_enum! { IndentStyle:
71     // First line on the same line as the opening brace, all lines aligned with
72     // the first line.
73     Visual,
74     // First line is on a new line and all lines align with block indent.
75     Block,
76 }
77
78 configuration_option_enum! { Density:
79     // Fit as much on one line as possible.
80     Compressed,
81     // Use more lines.
82     Tall,
83     // Try to compress if the body is empty.
84     CompressedIfEmpty,
85     // Place every item on a separate line.
86     Vertical,
87 }
88
89 configuration_option_enum! { TypeDensity:
90     // No spaces around "=" and "+"
91     Compressed,
92     // Spaces around " = " and " + "
93     Wide,
94 }
95
96
97 impl Density {
98     pub fn to_list_tactic(self) -> ListTactic {
99         match self {
100             Density::Compressed => ListTactic::Mixed,
101             Density::Tall | Density::CompressedIfEmpty => ListTactic::HorizontalVertical,
102             Density::Vertical => ListTactic::Vertical,
103         }
104     }
105 }
106
107 configuration_option_enum! { LicensePolicy:
108     // Do not place license text at top of files
109     NoLicense,
110     // Use the text in "license" field as the license
111     TextLicense,
112     // Use a text file as the license text
113     FileLicense,
114 }
115
116 configuration_option_enum! { MultilineStyle:
117     // Use horizontal layout if it fits in one line, fall back to vertical
118     PreferSingle,
119     // Use vertical layout
120     ForceMulti,
121 }
122
123 impl MultilineStyle {
124     pub fn to_list_tactic(self) -> ListTactic {
125         match self {
126             MultilineStyle::PreferSingle => ListTactic::HorizontalVertical,
127             MultilineStyle::ForceMulti => ListTactic::Vertical,
128         }
129     }
130 }
131
132 configuration_option_enum! { ReportTactic:
133     Always,
134     Unnumbered,
135     Never,
136 }
137
138 configuration_option_enum! { WriteMode:
139     // Backs the original file up and overwrites the original.
140     Replace,
141     // Overwrites original file without backup.
142     Overwrite,
143     // Writes the output to stdout.
144     Display,
145     // Writes the diff to stdout.
146     Diff,
147     // Displays how much of the input file was processed
148     Coverage,
149     // Unfancy stdout
150     Plain,
151     // Outputs a checkstyle XML file.
152     Checkstyle,
153 }
154
155 /// Trait for types that can be used in `Config`.
156 pub trait ConfigType: Sized {
157     /// Returns hint text for use in `Config::print_docs()`. For enum types, this is a
158     /// pipe-separated list of variants; for other types it returns "<type>".
159     fn doc_hint() -> String;
160 }
161
162 impl ConfigType for bool {
163     fn doc_hint() -> String {
164         String::from("<boolean>")
165     }
166 }
167
168 impl ConfigType for usize {
169     fn doc_hint() -> String {
170         String::from("<unsigned integer>")
171     }
172 }
173
174 impl ConfigType for isize {
175     fn doc_hint() -> String {
176         String::from("<signed integer>")
177     }
178 }
179
180 impl ConfigType for String {
181     fn doc_hint() -> String {
182         String::from("<string>")
183     }
184 }
185
186 impl ConfigType for FileLines {
187     fn doc_hint() -> String {
188         String::from("<json>")
189     }
190 }
191
192 pub struct ConfigHelpItem {
193     option_name: &'static str,
194     doc_string: &'static str,
195     variant_names: String,
196     default: &'static str,
197 }
198
199 impl ConfigHelpItem {
200     pub fn option_name(&self) -> &'static str {
201         self.option_name
202     }
203
204     pub fn doc_string(&self) -> &'static str {
205         self.doc_string
206     }
207
208     pub fn variant_names(&self) -> &String {
209         &self.variant_names
210     }
211
212     pub fn default(&self) -> &'static str {
213         self.default
214     }
215 }
216
217 macro_rules! create_config {
218     ($($i:ident: $ty:ty, $def:expr, $( $dstring:expr ),+ );+ $(;)*) => (
219         #[derive(Clone)]
220         pub struct Config {
221             // For each config item, we store a bool indicating whether it has
222             // been accessed and the value, and a bool whether the option was
223             // manually initialised, or taken from the default,
224             $($i: (Cell<bool>, bool, $ty)),+
225         }
226
227         // Just like the Config struct but with each property wrapped
228         // as Option<T>. This is used to parse a rustfmt.toml that doesn't
229         // specity all properties of `Config`.
230         // We first parse into `PartialConfig`, then create a default `Config`
231         // and overwrite the properties with corresponding values from `PartialConfig`.
232         #[derive(Deserialize, Serialize, Clone)]
233         pub struct PartialConfig {
234             $(pub $i: Option<$ty>),+
235         }
236
237         impl PartialConfig {
238             pub fn to_toml(&self) -> Result<String, String> {
239                 // file_lines can't be specified in TOML
240                 let mut cloned = self.clone();
241                 cloned.file_lines = None;
242
243                 toml::to_string(&cloned)
244                     .map_err(|e| format!("Could not output config: {}", e.to_string()))
245             }
246         }
247
248         // Macro hygiene won't allow us to make `set_$i()` methods on Config
249         // for each item, so this struct is used to give the API to set values:
250         // `config.get().option(false)`. It's pretty ugly. Consider replacing
251         // with `config.set_option(false)` if we ever get a stable/usable
252         // `concat_idents!()`.
253         pub struct ConfigSetter<'a>(&'a mut Config);
254
255         impl<'a> ConfigSetter<'a> {
256             $(
257             pub fn $i(&mut self, value: $ty) {
258                 (self.0).$i.2 = value;
259             }
260             )+
261         }
262
263         // Query each option, returns true if the user set the option, false if
264         // a default was used.
265         pub struct ConfigWasSet<'a>(&'a Config);
266
267         impl<'a> ConfigWasSet<'a> {
268             $(
269             pub fn $i(&self) -> bool {
270                 (self.0).$i.1
271             }
272             )+
273         }
274
275         impl Config {
276
277             $(
278             pub fn $i(&self) -> $ty {
279                 self.$i.0.set(true);
280                 self.$i.2.clone()
281             }
282             )+
283
284             pub fn set<'a>(&'a mut self) -> ConfigSetter<'a> {
285                 ConfigSetter(self)
286             }
287
288             pub fn was_set<'a>(&'a self) -> ConfigWasSet<'a> {
289                 ConfigWasSet(self)
290             }
291
292             fn fill_from_parsed_config(mut self, parsed: PartialConfig) -> Config {
293             $(
294                 if let Some(val) = parsed.$i {
295                     self.$i.1 = true;
296                     self.$i.2 = val;
297                 }
298             )+
299                 self
300             }
301
302             pub fn from_toml(toml: &str) -> Result<Config, String> {
303                 let parsed: toml::Value =
304                     toml.parse().map_err(|e| format!("Could not parse TOML: {}", e))?;
305                 let mut err: String = String::new();
306                 {
307                     let table = parsed
308                         .as_table()
309                         .ok_or(String::from("Parsed config was not table"))?;
310                     for (key, _) in table {
311                         match &**key {
312                             $(
313                                 stringify!($i) => (),
314                             )+
315                                 _ => {
316                                     let msg =
317                                         &format!("Warning: Unknown configuration option `{}`\n",
318                                                  key);
319                                     err.push_str(msg)
320                                 }
321                         }
322                     }
323                 }
324                 match parsed.try_into() {
325                     Ok(parsed_config) =>
326                         Ok(Config::default().fill_from_parsed_config(parsed_config)),
327                     Err(e) => {
328                         err.push_str("Error: Decoding config file failed:\n");
329                         err.push_str(format!("{}\n", e).as_str());
330                         err.push_str("Please check your config file.\n");
331                         Err(err)
332                     }
333                 }
334             }
335
336             pub fn used_options(&self) -> PartialConfig {
337                 PartialConfig {
338                     $(
339                         $i: if self.$i.0.get() {
340                                 Some(self.$i.2.clone())
341                             } else {
342                                 None
343                             },
344                     )+
345                 }
346             }
347
348             pub fn all_options(&self) -> PartialConfig {
349                 PartialConfig {
350                     $(
351                         $i: Some(self.$i.2.clone()),
352                     )+
353                 }
354             }
355
356             pub fn override_value(&mut self, key: &str, val: &str)
357             {
358                 match key {
359                     $(
360                         stringify!($i) => {
361                             self.$i.2 = val.parse::<$ty>()
362                                 .expect(&format!("Failed to parse override for {} (\"{}\") as a {}",
363                                                  stringify!($i),
364                                                  val,
365                                                  stringify!($ty)));
366                         }
367                     )+
368                     _ => panic!("Unknown config key in override: {}", key)
369                 }
370             }
371
372             /// Construct a `Config` from the toml file specified at `file_path`.
373             ///
374             /// This method only looks at the provided path, for a method that
375             /// searches parents for a `rustfmt.toml` see `from_resolved_toml_path`.
376             ///
377             /// Return a `Config` if the config could be read and parsed from
378             /// the file, Error otherwise.
379             pub fn from_toml_path(file_path: &Path) -> Result<Config, Error> {
380                 let mut file = File::open(&file_path)?;
381                 let mut toml = String::new();
382                 file.read_to_string(&mut toml)?;
383                 Config::from_toml(&toml).map_err(|err| Error::new(ErrorKind::InvalidData, err))
384             }
385
386             /// Resolve the config for input in `dir`.
387             ///
388             /// Searches for `rustfmt.toml` beginning with `dir`, and
389             /// recursively checking parents of `dir` if no config file is found.
390             /// If no config file exists in `dir` or in any parent, a
391             /// default `Config` will be returned (and the returned path will be empty).
392             ///
393             /// Returns the `Config` to use, and the path of the project file if there was
394             /// one.
395             pub fn from_resolved_toml_path(dir: &Path) -> Result<(Config, Option<PathBuf>), Error> {
396
397                 /// Try to find a project file in the given directory and its parents.
398                 /// Returns the path of a the nearest project file if one exists,
399                 /// or `None` if no project file was found.
400                 fn resolve_project_file(dir: &Path) -> Result<Option<PathBuf>, Error> {
401                     let mut current = if dir.is_relative() {
402                         env::current_dir()?.join(dir)
403                     } else {
404                         dir.to_path_buf()
405                     };
406
407                     current = fs::canonicalize(current)?;
408
409                     loop {
410                         match get_toml_path(&current) {
411                             Ok(Some(path)) => return Ok(Some(path)),
412                             Err(e) => return Err(e),
413                             _ => ()
414                         }
415
416                         // If the current directory has no parent, we're done searching.
417                         if !current.pop() {
418                             return Ok(None);
419                         }
420                     }
421                 }
422
423                 match resolve_project_file(dir)? {
424                     None => Ok((Config::default(), None)),
425                     Some(path) => Config::from_toml_path(&path).map(|config| (config, Some(path))),
426                 }
427             }
428
429
430             pub fn print_docs() {
431                 use std::cmp;
432                 let max = 0;
433                 $( let max = cmp::max(max, stringify!($i).len()+1); )+
434                 let mut space_str = String::with_capacity(max);
435                 for _ in 0..max {
436                     space_str.push(' ');
437                 }
438                 println!("Configuration Options:");
439                 $(
440                     let name_raw = stringify!($i);
441                     let mut name_out = String::with_capacity(max);
442                     for _ in name_raw.len()..max-1 {
443                         name_out.push(' ')
444                     }
445                     name_out.push_str(name_raw);
446                     name_out.push(' ');
447                     println!("{}{} Default: {:?}",
448                              name_out,
449                              <$ty>::doc_hint(),
450                              $def);
451                     $(
452                         println!("{}{}", space_str, $dstring);
453                     )+
454                     println!("");
455                 )+
456             }
457         }
458
459         // Template for the default configuration
460         impl Default for Config {
461             fn default() -> Config {
462                 Config {
463                     $(
464                         $i: (Cell::new(false), false, $def),
465                     )+
466                 }
467             }
468         }
469     )
470 }
471
472 /// Check for the presence of known config file names (`rustfmt.toml, `.rustfmt.toml`) in `dir`
473 ///
474 /// Return the path if a config file exists, empty if no file exists, and Error for IO errors
475 pub fn get_toml_path(dir: &Path) -> Result<Option<PathBuf>, Error> {
476     const CONFIG_FILE_NAMES: [&'static str; 2] = [".rustfmt.toml", "rustfmt.toml"];
477     for config_file_name in &CONFIG_FILE_NAMES {
478         let config_file = dir.join(config_file_name);
479         match fs::metadata(&config_file) {
480             // Only return if it's a file to handle the unlikely situation of a directory named
481             // `rustfmt.toml`.
482             Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
483             // Return the error if it's something other than `NotFound`; otherwise we didn't
484             // find the project file yet, and continue searching.
485             Err(e) => {
486                 if e.kind() != ErrorKind::NotFound {
487                     return Err(e);
488                 }
489             }
490             _ => {}
491         }
492     }
493     Ok(None)
494 }
495
496
497
498 create_config! {
499     verbose: bool, false, "Use verbose output";
500     disable_all_formatting: bool, false, "Don't reformat anything";
501     skip_children: bool, false, "Don't reformat out of line modules";
502     file_lines: FileLines, FileLines::all(),
503         "Lines to format; this is not supported in rustfmt.toml, and can only be specified \
504          via the --file-lines option";
505     max_width: usize, 100, "Maximum width of each line";
506     error_on_line_overflow: bool, true, "Error if unable to get all lines within max_width";
507     tab_spaces: usize, 4, "Number of spaces per tab";
508     fn_call_width: usize, 60,
509         "Maximum width of the args of a function call before falling back to vertical formatting";
510     struct_lit_width: usize, 18,
511         "Maximum width in the body of a struct lit before falling back to vertical formatting";
512     struct_variant_width: usize, 35,
513         "Maximum width in the body of a struct variant before falling back to vertical formatting";
514     force_explicit_abi: bool, true, "Always print the abi for extern items";
515     newline_style: NewlineStyle, NewlineStyle::Unix, "Unix or Windows line endings";
516     fn_brace_style: BraceStyle, BraceStyle::SameLineWhere, "Brace style for functions";
517     item_brace_style: BraceStyle, BraceStyle::SameLineWhere, "Brace style for structs and enums";
518     control_style: Style, Style::Rfc, "Indent style for control flow statements";
519     control_brace_style: ControlBraceStyle, ControlBraceStyle::AlwaysSameLine,
520         "Brace style for control flow constructs";
521     impl_empty_single_line: bool, true, "Put empty-body implementations on a single line";
522     trailing_comma: SeparatorTactic, SeparatorTactic::Vertical,
523         "How to handle trailing commas for lists";
524     fn_empty_single_line: bool, true, "Put empty-body functions on a single line";
525     fn_single_line: bool, false, "Put single-expression functions on a single line";
526     fn_return_indent: ReturnIndent, ReturnIndent::WithArgs,
527         "Location of return type in function declaration";
528     fn_args_paren_newline: bool, false, "If function argument parenthesis goes on a newline";
529     fn_args_density: Density, Density::Tall, "Argument density in functions";
530     fn_args_layout: IndentStyle, IndentStyle::Block,
531         "Layout of function arguments and tuple structs";
532     array_layout: IndentStyle, IndentStyle::Block, "Indent on arrays";
533     array_width: usize, 60,
534         "Maximum width of an array literal before falling back to vertical formatting";
535     array_horizontal_layout_threshold: usize, 0,
536         "How many elements array must have before rustfmt uses horizontal layout.";
537     type_punctuation_density: TypeDensity, TypeDensity::Wide,
538         "Determines if '+' or '=' are wrapped in spaces in the punctuation of types";
539     where_style: Style, Style::Rfc, "Overall strategy for where clauses";
540     // TODO:
541     // 1. Should we at least try to put the where clause on the same line as the rest of the
542     // function decl?
543     // 2. Currently options `Tall` and `Vertical` produce the same output.
544     where_density: Density, Density::CompressedIfEmpty, "Density of a where clause";
545     where_layout: ListTactic, ListTactic::Vertical, "Element layout inside a where clause";
546     where_pred_indent: IndentStyle, IndentStyle::Visual,
547         "Indentation style of a where predicate";
548     generics_indent: IndentStyle, IndentStyle::Block, "Indentation of generics";
549     struct_lit_style: IndentStyle, IndentStyle::Block, "Style of struct definition";
550     struct_lit_multiline_style: MultilineStyle, MultilineStyle::PreferSingle,
551         "Multiline style on literal structs";
552     fn_call_style: IndentStyle, IndentStyle::Block, "Indentation for function calls, etc.";
553     report_todo: ReportTactic, ReportTactic::Never,
554         "Report all, none or unnumbered occurrences of TODO in source file comments";
555     report_fixme: ReportTactic, ReportTactic::Never,
556         "Report all, none or unnumbered occurrences of FIXME in source file comments";
557     chain_indent: IndentStyle, IndentStyle::Block, "Indentation of chain";
558     chain_one_line_max: usize, 60, "Maximum length of a chain to fit on a single line";
559     chain_split_single_child: bool, false, "Split a chain with a single child if its length \
560                                             exceeds `chain_one_line_max`";
561     reorder_imports: bool, false, "Reorder import statements alphabetically";
562     reorder_imports_in_group: bool, false, "Reorder import statements in group";
563     reorder_imported_names: bool, false,
564         "Reorder lists of names in import statements alphabetically";
565     single_line_if_else_max_width: usize, 50, "Maximum line length for single line if-else \
566                                                 expressions. A value of zero means always break \
567                                                 if-else expressions.";
568     format_strings: bool, false, "Format string literals where necessary";
569     force_format_strings: bool, false, "Always format string literals";
570     take_source_hints: bool, false, "Retain some formatting characteristics from the source code";
571     hard_tabs: bool, false, "Use tab characters for indentation, spaces for alignment";
572     wrap_comments: bool, false, "Break comments to fit on the line";
573     comment_width: usize, 80, "Maximum length of comments. No effect unless wrap_comments = true";
574     normalize_comments: bool, false, "Convert /* */ comments to // comments where possible";
575     wrap_match_arms: bool, true, "Wrap multiline match arms in blocks";
576     match_block_trailing_comma: bool, false,
577         "Put a trailing comma after a block based match arm (non-block arms are not affected)";
578     indent_match_arms: bool, true, "Indent match arms instead of keeping them at the same \
579                                     indentation level as the match keyword";
580     closure_block_indent_threshold: isize, 7, "How many lines a closure must have before it is \
581                                                block indented. -1 means never use block indent.";
582     space_before_type_annotation: bool, false,
583         "Leave a space before the colon in a type annotation";
584     space_after_type_annotation_colon: bool, true,
585         "Leave a space after the colon in a type annotation";
586     space_before_struct_lit_field_colon: bool, false,
587         "Leave a space before the colon in a struct literal field";
588     space_after_struct_lit_field_colon: bool, true,
589         "Leave a space after the colon in a struct literal field";
590     space_before_bound: bool, false, "Leave a space before the colon in a trait or lifetime bound";
591     space_after_bound_colon: bool, true,
592         "Leave a space after the colon in a trait or lifetime bound";
593     spaces_around_ranges: bool, false, "Put spaces around the  .. and ... range operators";
594     spaces_within_angle_brackets: bool, false, "Put spaces within non-empty generic arguments";
595     spaces_within_square_brackets: bool, false, "Put spaces within non-empty square brackets";
596     spaces_within_parens: bool, false, "Put spaces within non-empty parentheses";
597     use_try_shorthand: bool, false, "Replace uses of the try! macro by the ? shorthand";
598     write_mode: WriteMode, WriteMode::Replace,
599         "What Write Mode to use when none is supplied: Replace, Overwrite, Display, Diff, Coverage";
600     condense_wildcard_suffixes: bool, false, "Replace strings of _ wildcards by a single .. in \
601                                               tuple patterns";
602     combine_control_expr: bool, true, "Combine control expressions with funciton calls.";
603     struct_field_align_threshold: usize, 0, "Align struct fields if their diffs fits within \
604                                              threshold."
605 }
606
607 #[cfg(test)]
608 mod test {
609     use super::Config;
610
611     #[test]
612     fn test_config_set() {
613         let mut config = Config::default();
614         config.set().verbose(false);
615         assert_eq!(config.verbose(), false);
616         config.set().verbose(true);
617         assert_eq!(config.verbose(), true);
618     }
619
620     #[test]
621     fn test_config_used_to_toml() {
622         let config = Config::default();
623
624         let verbose = config.verbose();
625         let skip_children = config.skip_children();
626
627         let used_options = config.used_options();
628         let toml = used_options.to_toml().unwrap();
629         assert_eq!(
630             toml,
631             format!("verbose = {}\nskip_children = {}\n", verbose, skip_children)
632         );
633     }
634
635     #[test]
636     fn test_was_set() {
637         let config = Config::from_toml("hard_tabs = true").unwrap();
638
639         assert_eq!(config.was_set().hard_tabs(), true);
640         assert_eq!(config.was_set().verbose(), false);
641     }
642 }