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.
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.
16 use std::io::{Error, ErrorKind, Read};
17 use std::path::{Path, PathBuf};
19 use file_lines::FileLines;
20 use lists::{ListTactic, SeparatorPlace, SeparatorTactic};
24 macro_rules! is_nightly_channel {
26 env::var("CFG_RELEASE_CHANNEL")
27 .map(|c| c == "nightly")
32 macro_rules! configuration_option_enum{
33 ($e:ident: $( $x:ident ),+ $(,)*) => {
34 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
39 impl_enum_serialize_and_deserialize!($e, $( $x ),+);
43 configuration_option_enum! { Style:
44 Rfc, // Follow the style RFCs style.
45 Legacy, // Follow the traditional Rustfmt style.
48 configuration_option_enum! { NewlineStyle:
51 Native, // \r\n in Windows, \n on other platforms
54 configuration_option_enum! { BraceStyle:
57 // Prefer same line except where there is a where clause, in which case force
58 // the brace to the next line.
62 configuration_option_enum! { ControlBraceStyle:
63 // K&R style, Rust community default
71 // How to indent a function's return type.
72 configuration_option_enum! { ReturnIndent:
73 // Aligned with the arguments
75 // Aligned with the where clause
79 configuration_option_enum! { IndentStyle:
80 // First line on the same line as the opening brace, all lines aligned with
83 // First line is on a new line and all lines align with block indent.
87 configuration_option_enum! { Density:
88 // Fit as much on one line as possible.
92 // Try to compress if the body is empty.
94 // Place every item on a separate line.
98 configuration_option_enum! { TypeDensity:
99 // No spaces around "=" and "+"
101 // Spaces around " = " and " + "
107 pub fn to_list_tactic(self) -> ListTactic {
109 Density::Compressed => ListTactic::Mixed,
110 Density::Tall | Density::CompressedIfEmpty => ListTactic::HorizontalVertical,
111 Density::Vertical => ListTactic::Vertical,
116 configuration_option_enum! { MultilineStyle:
117 // Use horizontal layout if it fits in one line, fall back to vertical
119 // Use vertical layout
123 impl MultilineStyle {
124 pub fn to_list_tactic(self) -> ListTactic {
126 MultilineStyle::PreferSingle => ListTactic::HorizontalVertical,
127 MultilineStyle::ForceMulti => ListTactic::Vertical,
132 configuration_option_enum! { ReportTactic:
138 configuration_option_enum! { WriteMode:
139 // Backs the original file up and overwrites the original.
141 // Overwrites original file without backup.
143 // Writes the output to stdout.
145 // Writes the diff to stdout.
147 // Displays how much of the input file was processed
151 // Outputs a checkstyle XML file.
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;
162 impl ConfigType for bool {
163 fn doc_hint() -> String {
164 String::from("<boolean>")
168 impl ConfigType for usize {
169 fn doc_hint() -> String {
170 String::from("<unsigned integer>")
174 impl ConfigType for isize {
175 fn doc_hint() -> String {
176 String::from("<signed integer>")
180 impl ConfigType for String {
181 fn doc_hint() -> String {
182 String::from("<string>")
186 impl ConfigType for FileLines {
187 fn doc_hint() -> String {
188 String::from("<json>")
192 pub struct ConfigHelpItem {
193 option_name: &'static str,
194 doc_string: &'static str,
195 variant_names: String,
196 default: &'static str,
199 impl ConfigHelpItem {
200 pub fn option_name(&self) -> &'static str {
204 pub fn doc_string(&self) -> &'static str {
208 pub fn variant_names(&self) -> &String {
212 pub fn default(&self) -> &'static str {
217 macro_rules! create_config {
218 ($($i:ident: $ty:ty, $def:expr, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => (
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, bool)),+
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 // specify 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>),+
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;
243 toml::to_string(&cloned)
244 .map_err(|e| format!("Could not output config: {}", e.to_string()))
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);
255 impl<'a> ConfigSetter<'a> {
257 pub fn $i(&mut self, value: $ty) {
258 (self.0).$i.2 = value;
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);
267 impl<'a> ConfigWasSet<'a> {
269 pub fn $i(&self) -> bool {
276 pub fn version_meets_requirement(&self, error_summary: &mut Summary) -> bool {
277 if self.was_set().required_version() {
278 let version = env!("CARGO_PKG_VERSION");
279 let required_version = self.required_version();
280 if version != required_version {
282 "Error: rustfmt version ({}) doesn't match the required version ({})",
286 error_summary.add_formatting_error();
295 pub fn $i(&self) -> $ty {
301 pub fn set<'a>(&'a mut self) -> ConfigSetter<'a> {
305 pub fn was_set<'a>(&'a self) -> ConfigWasSet<'a> {
309 fn fill_from_parsed_config(mut self, parsed: PartialConfig) -> Config {
311 if let Some(val) = parsed.$i {
316 if is_nightly_channel!() {
320 println!("Warning: can't set some features as unstable \
321 features are only available in nightly channel.");
329 pub fn from_toml(toml: &str) -> Result<Config, String> {
330 let parsed: toml::Value =
331 toml.parse().map_err(|e| format!("Could not parse TOML: {}", e))?;
332 let mut err: String = String::new();
336 .ok_or(String::from("Parsed config was not table"))?;
337 for key in table.keys() {
340 stringify!($i) => (),
344 &format!("Warning: Unknown configuration option `{}`\n",
351 match parsed.try_into() {
353 Ok(Config::default().fill_from_parsed_config(parsed_config)),
355 err.push_str("Error: Decoding config file failed:\n");
356 err.push_str(format!("{}\n", e).as_str());
357 err.push_str("Please check your config file.\n");
363 pub fn used_options(&self) -> PartialConfig {
366 $i: if self.$i.0.get() {
367 Some(self.$i.2.clone())
375 pub fn all_options(&self) -> PartialConfig {
378 $i: Some(self.$i.2.clone()),
383 pub fn override_value(&mut self, key: &str, val: &str)
388 self.$i.2 = val.parse::<$ty>()
389 .expect(&format!("Failed to parse override for {} (\"{}\") as a {}",
395 _ => panic!("Unknown config key in override: {}", key)
399 /// Construct a `Config` from the toml file specified at `file_path`.
401 /// This method only looks at the provided path, for a method that
402 /// searches parents for a `rustfmt.toml` see `from_resolved_toml_path`.
404 /// Return a `Config` if the config could be read and parsed from
405 /// the file, Error otherwise.
406 pub fn from_toml_path(file_path: &Path) -> Result<Config, Error> {
407 let mut file = File::open(&file_path)?;
408 let mut toml = String::new();
409 file.read_to_string(&mut toml)?;
410 Config::from_toml(&toml).map_err(|err| Error::new(ErrorKind::InvalidData, err))
413 /// Resolve the config for input in `dir`.
415 /// Searches for `rustfmt.toml` beginning with `dir`, and
416 /// recursively checking parents of `dir` if no config file is found.
417 /// If no config file exists in `dir` or in any parent, a
418 /// default `Config` will be returned (and the returned path will be empty).
420 /// Returns the `Config` to use, and the path of the project file if there was
422 pub fn from_resolved_toml_path(dir: &Path) -> Result<(Config, Option<PathBuf>), Error> {
424 /// Try to find a project file in the given directory and its parents.
425 /// Returns the path of a the nearest project file if one exists,
426 /// or `None` if no project file was found.
427 fn resolve_project_file(dir: &Path) -> Result<Option<PathBuf>, Error> {
428 let mut current = if dir.is_relative() {
429 env::current_dir()?.join(dir)
434 current = fs::canonicalize(current)?;
437 match get_toml_path(¤t) {
438 Ok(Some(path)) => return Ok(Some(path)),
439 Err(e) => return Err(e),
443 // If the current directory has no parent, we're done searching.
450 match resolve_project_file(dir)? {
451 None => Ok((Config::default(), None)),
452 Some(path) => Config::from_toml_path(&path).map(|config| (config, Some(path))),
457 pub fn print_docs() {
460 $( let max = cmp::max(max, stringify!($i).len()+1); )+
461 let mut space_str = String::with_capacity(max);
465 println!("Configuration Options:");
467 let name_raw = stringify!($i);
468 let mut name_out = String::with_capacity(max);
469 for _ in name_raw.len()..max-1 {
472 name_out.push_str(name_raw);
474 println!("{}{} Default: {:?}",
479 println!("{}{}", space_str, $dstring);
486 // Template for the default configuration
487 impl Default for Config {
488 fn default() -> Config {
491 $i: (Cell::new(false), false, $def, $stb),
499 /// Check for the presence of known config file names (`rustfmt.toml, `.rustfmt.toml`) in `dir`
501 /// Return the path if a config file exists, empty if no file exists, and Error for IO errors
502 pub fn get_toml_path(dir: &Path) -> Result<Option<PathBuf>, Error> {
503 const CONFIG_FILE_NAMES: [&str; 2] = [".rustfmt.toml", "rustfmt.toml"];
504 for config_file_name in &CONFIG_FILE_NAMES {
505 let config_file = dir.join(config_file_name);
506 match fs::metadata(&config_file) {
507 // Only return if it's a file to handle the unlikely situation of a directory named
509 Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
510 // Return the error if it's something other than `NotFound`; otherwise we didn't
511 // find the project file yet, and continue searching.
512 Err(e) => if e.kind() != ErrorKind::NotFound {
524 unstable_features: bool, false, true,
525 "Enables unstable features. Only available on nightly channel";
526 verbose: bool, false, false, "Use verbose output";
527 disable_all_formatting: bool, false, false, "Don't reformat anything";
528 skip_children: bool, false, false, "Don't reformat out of line modules";
529 file_lines: FileLines, FileLines::all(), false,
530 "Lines to format; this is not supported in rustfmt.toml, and can only be specified \
531 via the --file-lines option";
532 max_width: usize, 100, false, "Maximum width of each line";
533 error_on_line_overflow: bool, true, false, "Error if unable to get all lines within max_width";
534 error_on_line_overflow_comments: bool, true, false,
535 "Error if unable to get comments within max_width";
536 tab_spaces: usize, 4, false, "Number of spaces per tab";
537 fn_call_width: usize, 60, false,
538 "Maximum width of the args of a function call before falling back to vertical formatting";
539 struct_lit_width: usize, 18, false,
540 "Maximum width in the body of a struct lit before falling back to vertical formatting";
541 struct_variant_width: usize, 35, false,
542 "Maximum width in the body of a struct variant before falling back to vertical formatting";
543 force_explicit_abi: bool, true, false, "Always print the abi for extern items";
544 newline_style: NewlineStyle, NewlineStyle::Unix, false, "Unix or Windows line endings";
545 fn_brace_style: BraceStyle, BraceStyle::SameLineWhere, false, "Brace style for functions";
546 item_brace_style: BraceStyle, BraceStyle::SameLineWhere, false,
547 "Brace style for structs and enums";
548 control_style: Style, Style::Rfc, false, "Indent style for control flow statements";
549 control_brace_style: ControlBraceStyle, ControlBraceStyle::AlwaysSameLine, false,
550 "Brace style for control flow constructs";
551 impl_empty_single_line: bool, true, false, "Put empty-body implementations on a single line";
552 trailing_comma: SeparatorTactic, SeparatorTactic::Vertical, false,
553 "How to handle trailing commas for lists";
554 trailing_semicolon: bool, true, false,
555 "Add trailing semicolon after break, continue and return";
556 fn_empty_single_line: bool, true, false, "Put empty-body functions on a single line";
557 fn_single_line: bool, false, false, "Put single-expression functions on a single line";
558 fn_return_indent: ReturnIndent, ReturnIndent::WithArgs, false,
559 "Location of return type in function declaration";
560 fn_args_paren_newline: bool, false, false, "If function argument parenthesis goes on a newline";
561 fn_args_density: Density, Density::Tall, false, "Argument density in functions";
562 fn_args_indent: IndentStyle, IndentStyle::Block, false,
563 "Layout of function arguments and tuple structs";
564 array_indent: IndentStyle, IndentStyle::Block, false, "Indent on arrays";
565 array_width: usize, 60, false,
566 "Maximum width of an array literal before falling back to vertical formatting";
567 array_horizontal_layout_threshold: usize, 0, false,
568 "How many elements array must have before rustfmt uses horizontal layout.";
569 type_punctuation_density: TypeDensity, TypeDensity::Wide, false,
570 "Determines if '+' or '=' are wrapped in spaces in the punctuation of types";
571 where_style: Style, Style::Rfc, false, "Overall strategy for where clauses";
573 // 1. Should we at least try to put the where clause on the same line as the rest of the
575 // 2. Currently options `Tall` and `Vertical` produce the same output.
576 where_density: Density, Density::Vertical, false, "Density of a where clause";
577 where_single_line: bool, false, false, "To force single line where layout";
578 where_layout: ListTactic, ListTactic::Vertical, false, "Element layout inside a where clause";
579 where_pred_indent: IndentStyle, IndentStyle::Visual, false,
580 "Indentation style of a where predicate";
581 generics_indent: IndentStyle, IndentStyle::Block, false, "Indentation of generics";
582 struct_lit_indent: IndentStyle, IndentStyle::Block, false, "Style of struct definition";
583 struct_lit_multiline_style: MultilineStyle, MultilineStyle::PreferSingle, false,
584 "Multiline style on literal structs";
585 fn_call_indent: IndentStyle, IndentStyle::Block, false, "Indentation for function calls, etc.";
586 report_todo: ReportTactic, ReportTactic::Never, false,
587 "Report all, none or unnumbered occurrences of TODO in source file comments";
588 report_fixme: ReportTactic, ReportTactic::Never, false,
589 "Report all, none or unnumbered occurrences of FIXME in source file comments";
590 chain_indent: IndentStyle, IndentStyle::Block, false, "Indentation of chain";
591 chain_width: usize, 60, false, "Maximum length of a chain to fit on a single line";
592 chain_split_single_child: bool, false, false, "Split a chain with a single child if its length \
593 exceeds `chain_width`";
594 imports_indent: IndentStyle, IndentStyle::Visual, false, "Indent of imports";
595 imports_layout: ListTactic, ListTactic::Mixed, false, "Item layout inside a import block";
596 reorder_extern_crates: bool, true, false, "Reorder extern crate statements alphabetically";
597 reorder_extern_crates_in_group: bool, true, false, "Reorder extern crate statements in group";
598 reorder_imports: bool, false, false, "Reorder import statements alphabetically";
599 reorder_imports_in_group: bool, false, false, "Reorder import statements in group";
600 reorder_imported_names: bool, true, false,
601 "Reorder lists of names in import statements alphabetically";
602 single_line_if_else_max_width: usize, 50, false, "Maximum line length for single line if-else \
603 expressions. A value of zero means always break \
604 if-else expressions.";
605 format_strings: bool, false, false, "Format string literals where necessary";
606 force_format_strings: bool, false, false, "Always format string literals";
607 take_source_hints: bool, false, false,
608 "Retain some formatting characteristics from the source code";
609 hard_tabs: bool, false, false, "Use tab characters for indentation, spaces for alignment";
610 wrap_comments: bool, false, false, "Break comments to fit on the line";
611 comment_width: usize, 80, false,
612 "Maximum length of comments. No effect unless wrap_comments = true";
613 normalize_comments: bool, false, false, "Convert /* */ comments to // comments where possible";
614 wrap_match_arms: bool, true, false, "Wrap the body of arms in blocks when it does not fit on \
615 the same line with the pattern of arms";
616 match_block_trailing_comma: bool, false, false,
617 "Put a trailing comma after a block based match arm (non-block arms are not affected)";
618 match_arm_forces_newline: bool, false, false,
619 "Force match arm bodies to be in a new lines";
620 indent_match_arms: bool, true, false, "Indent match arms instead of keeping them at the same \
621 indentation level as the match keyword";
622 match_pattern_separator_break_point: SeparatorPlace, SeparatorPlace::Back, false,
623 "Put a match sub-patterns' separator in front or back.";
624 closure_block_indent_threshold: isize, 7, false,
625 "How many lines a closure must have before it is block indented. \
626 -1 means never use block indent.";
627 space_before_type_annotation: bool, false, false,
628 "Leave a space before the colon in a type annotation";
629 space_after_type_annotation_colon: bool, true, false,
630 "Leave a space after the colon in a type annotation";
631 space_before_struct_lit_field_colon: bool, false, false,
632 "Leave a space before the colon in a struct literal field";
633 space_after_struct_lit_field_colon: bool, true, false,
634 "Leave a space after the colon in a struct literal field";
635 space_before_bound: bool, false, false,
636 "Leave a space before the colon in a trait or lifetime bound";
637 space_after_bound_colon: bool, true, false,
638 "Leave a space after the colon in a trait or lifetime bound";
639 spaces_around_ranges: bool, false, false, "Put spaces around the .. and ... range operators";
640 spaces_within_angle_brackets: bool, false, false,
641 "Put spaces within non-empty generic arguments";
642 spaces_within_square_brackets: bool, false, false,
643 "Put spaces within non-empty square brackets";
644 spaces_within_parens: bool, false, false, "Put spaces within non-empty parentheses";
645 use_try_shorthand: bool, false, false, "Replace uses of the try! macro by the ? shorthand";
646 write_mode: WriteMode, WriteMode::Overwrite, false,
647 "What Write Mode to use when none is supplied: \
648 Replace, Overwrite, Display, Plain, Diff, Coverage";
649 condense_wildcard_suffixes: bool, false, false, "Replace strings of _ wildcards by a single .. \
651 combine_control_expr: bool, true, false, "Combine control expressions with function calls.";
652 struct_field_align_threshold: usize, 0, false, "Align struct fields if their diffs fits within \
654 remove_blank_lines_at_start_or_end_of_block: bool, true, false,
655 "Remove blank lines at start or end of a block";
656 attributes_on_same_line_as_field: bool, true, false,
657 "Try to put attributes on the same line as fields.";
658 attributes_on_same_line_as_variant: bool, true, false,
659 "Try to put attributes on the same line as variants in enum declarations.";
660 multiline_closure_forces_block: bool, false, false,
661 "Force multiline closure bodies to be wrapped in a block";
662 multiline_match_arm_forces_block: bool, false, false,
663 "Force multiline match arm bodies to be wrapped in a block";
664 merge_derives: bool, true, false, "Merge multiple `#[derive(...)]` into a single one";
665 binop_separator: SeparatorPlace, SeparatorPlace::Front, false,
666 "Where to put a binary operator when a binary expression goes multiline.";
667 required_version: String, env!("CARGO_PKG_VERSION").to_owned(), false,
668 "Require a specific version of rustfmt."
676 fn test_config_set() {
677 let mut config = Config::default();
678 config.set().verbose(false);
679 assert_eq!(config.verbose(), false);
680 config.set().verbose(true);
681 assert_eq!(config.verbose(), true);
685 fn test_config_used_to_toml() {
686 let config = Config::default();
688 let verbose = config.verbose();
689 let skip_children = config.skip_children();
691 let used_options = config.used_options();
692 let toml = used_options.to_toml().unwrap();
695 format!("verbose = {}\nskip_children = {}\n", verbose, skip_children)
701 let config = Config::from_toml("hard_tabs = true").unwrap();
703 assert_eq!(config.was_set().hard_tabs(), true);
704 assert_eq!(config.was_set().verbose(), false);
708 fn test_as_not_nightly_channel() {
709 let mut config = Config::default();
710 assert_eq!(config.was_set().unstable_features(), false);
711 config.set().unstable_features(true);
712 assert_eq!(config.was_set().unstable_features(), false);
716 fn test_as_nightly_channel() {
717 let v = ::std::env::var("CFG_RELEASE_CHANNEL").unwrap_or(String::from(""));
718 ::std::env::set_var("CFG_RELEASE_CHANNEL", "nightly");
719 let mut config = Config::default();
720 config.set().unstable_features(true);
721 assert_eq!(config.was_set().unstable_features(), false);
722 config.set().unstable_features(true);
723 assert_eq!(config.unstable_features(), true);
724 ::std::env::set_var("CFG_RELEASE_CHANNEL", v);
728 fn test_unstable_from_toml() {
729 let mut config = Config::from_toml("unstable_features = true").unwrap();
730 assert_eq!(config.was_set().unstable_features(), false);
731 let v = ::std::env::var("CFG_RELEASE_CHANNEL").unwrap_or(String::from(""));
732 ::std::env::set_var("CFG_RELEASE_CHANNEL", "nightly");
733 config = Config::from_toml("unstable_features = true").unwrap();
734 assert_eq!(config.was_set().unstable_features(), true);
735 assert_eq!(config.unstable_features(), true);
736 ::std::env::set_var("CFG_RELEASE_CHANNEL", v);