| use crate::config::file_lines::FileLines; |
| use crate::config::macro_names::MacroSelectors; |
| use crate::config::options::{IgnoreList, WidthHeuristics}; |
| |
| /// Trait for types that can be used in `Config`. |
| pub(crate) trait ConfigType: Sized { |
| /// Returns hint text for use in `Config::print_docs()`. For enum types, this is a |
| /// pipe-separated list of variants; for other types it returns `<type>`. |
| fn doc_hint() -> String; |
| |
| /// Return `true` if the variant (i.e. value of this type) is stable. |
| /// |
| /// By default, return true for all values. Enums annotated with `#[config_type]` |
| /// are automatically implemented, based on the `#[unstable_variant]` annotation. |
| fn stable_variant(&self) -> bool { |
| true |
| } |
| } |
| |
| impl ConfigType for bool { |
| fn doc_hint() -> String { |
| String::from("<boolean>") |
| } |
| } |
| |
| impl ConfigType for usize { |
| fn doc_hint() -> String { |
| String::from("<unsigned integer>") |
| } |
| } |
| |
| impl ConfigType for isize { |
| fn doc_hint() -> String { |
| String::from("<signed integer>") |
| } |
| } |
| |
| impl ConfigType for String { |
| fn doc_hint() -> String { |
| String::from("<string>") |
| } |
| } |
| |
| impl ConfigType for FileLines { |
| fn doc_hint() -> String { |
| String::from("<json>") |
| } |
| } |
| |
| impl ConfigType for MacroSelectors { |
| fn doc_hint() -> String { |
| String::from("[<string>, ...]") |
| } |
| } |
| |
| impl ConfigType for WidthHeuristics { |
| fn doc_hint() -> String { |
| String::new() |
| } |
| } |
| |
| impl ConfigType for IgnoreList { |
| fn doc_hint() -> String { |
| String::from("[<string>,..]") |
| } |
| } |
| |
| macro_rules! create_config { |
| // Options passed in to the macro. |
| // |
| // - $i: the ident name of the option |
| // - $ty: the type of the option value |
| // - $def: the default value of the option |
| // - $stb: true if the option is stable |
| // - $dstring: description of the option |
| ($($i:ident: $ty:ty, $def:expr, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => ( |
| #[cfg(test)] |
| use std::collections::HashSet; |
| use std::io::Write; |
| |
| use serde::{Deserialize, Serialize}; |
| |
| #[derive(Clone)] |
| #[allow(unreachable_pub)] |
| pub struct Config { |
| // For each config item, we store: |
| // |
| // - 0: true if the value has been access |
| // - 1: true if the option was manually initialized |
| // - 2: the option value |
| // - 3: true if the option is unstable |
| $($i: (Cell<bool>, bool, $ty, bool)),+ |
| } |
| |
| // Just like the Config struct but with each property wrapped |
| // as Option<T>. This is used to parse a rustfmt.toml that doesn't |
| // specify all properties of `Config`. |
| // We first parse into `PartialConfig`, then create a default `Config` |
| // and overwrite the properties with corresponding values from `PartialConfig`. |
| #[derive(Deserialize, Serialize, Clone)] |
| #[allow(unreachable_pub)] |
| pub struct PartialConfig { |
| $(pub $i: Option<$ty>),+ |
| } |
| |
| // Macro hygiene won't allow us to make `set_$i()` methods on Config |
| // for each item, so this struct is used to give the API to set values: |
| // `config.set().option(false)`. It's pretty ugly. Consider replacing |
| // with `config.set_option(false)` if we ever get a stable/usable |
| // `concat_idents!()`. |
| #[allow(unreachable_pub)] |
| pub struct ConfigSetter<'a>(&'a mut Config); |
| |
| impl<'a> ConfigSetter<'a> { |
| $( |
| #[allow(unreachable_pub)] |
| pub fn $i(&mut self, value: $ty) { |
| (self.0).$i.2 = value; |
| match stringify!($i) { |
| "max_width" |
| | "use_small_heuristics" |
| | "fn_call_width" |
| | "single_line_if_else_max_width" |
| | "single_line_let_else_max_width" |
| | "attr_fn_like_width" |
| | "struct_lit_width" |
| | "struct_variant_width" |
| | "array_width" |
| | "chain_width" => self.0.set_heuristics(), |
| "merge_imports" => self.0.set_merge_imports(), |
| "fn_args_layout" => self.0.set_fn_args_layout(), |
| &_ => (), |
| } |
| } |
| )+ |
| } |
| |
| // Query each option, returns true if the user set the option, false if |
| // a default was used. |
| #[allow(unreachable_pub)] |
| pub struct ConfigWasSet<'a>(&'a Config); |
| |
| impl<'a> ConfigWasSet<'a> { |
| $( |
| #[allow(unreachable_pub)] |
| pub fn $i(&self) -> bool { |
| (self.0).$i.1 |
| } |
| )+ |
| } |
| |
| impl Config { |
| $( |
| #[allow(unreachable_pub)] |
| pub fn $i(&self) -> $ty { |
| self.$i.0.set(true); |
| self.$i.2.clone() |
| } |
| )+ |
| |
| #[allow(unreachable_pub)] |
| pub fn set(&mut self) -> ConfigSetter<'_> { |
| ConfigSetter(self) |
| } |
| |
| #[allow(unreachable_pub)] |
| pub fn was_set(&self) -> ConfigWasSet<'_> { |
| ConfigWasSet(self) |
| } |
| |
| fn fill_from_parsed_config(mut self, parsed: PartialConfig, dir: &Path) -> Config { |
| $( |
| if let Some(option_value) = parsed.$i { |
| let option_stable = self.$i.3; |
| if $crate::config::config_type::is_stable_option_and_value( |
| stringify!($i), option_stable, &option_value |
| ) { |
| self.$i.1 = true; |
| self.$i.2 = option_value; |
| } |
| } |
| )+ |
| self.set_heuristics(); |
| self.set_ignore(dir); |
| self.set_merge_imports(); |
| self.set_fn_args_layout(); |
| self |
| } |
| |
| /// Returns a hash set initialized with every user-facing config option name. |
| #[cfg(test)] |
| pub(crate) fn hash_set() -> HashSet<String> { |
| let mut hash_set = HashSet::new(); |
| $( |
| hash_set.insert(stringify!($i).to_owned()); |
| )+ |
| hash_set |
| } |
| |
| pub(crate) fn is_valid_name(name: &str) -> bool { |
| match name { |
| $( |
| stringify!($i) => true, |
| )+ |
| _ => false, |
| } |
| } |
| |
| #[allow(unreachable_pub)] |
| pub fn is_valid_key_val(key: &str, val: &str) -> bool { |
| match key { |
| $( |
| stringify!($i) => val.parse::<$ty>().is_ok(), |
| )+ |
| _ => false, |
| } |
| } |
| |
| #[allow(unreachable_pub)] |
| pub fn used_options(&self) -> PartialConfig { |
| PartialConfig { |
| $( |
| $i: if self.$i.0.get() { |
| Some(self.$i.2.clone()) |
| } else { |
| None |
| }, |
| )+ |
| } |
| } |
| |
| #[allow(unreachable_pub)] |
| pub fn all_options(&self) -> PartialConfig { |
| PartialConfig { |
| $( |
| $i: Some(self.$i.2.clone()), |
| )+ |
| } |
| } |
| |
| #[allow(unreachable_pub)] |
| pub fn override_value(&mut self, key: &str, val: &str) |
| { |
| match key { |
| $( |
| stringify!($i) => { |
| let option_value = val.parse::<$ty>() |
| .expect(&format!("Failed to parse override for {} (\"{}\") as a {}", |
| stringify!($i), |
| val, |
| stringify!($ty))); |
| |
| // Users are currently allowed to set unstable |
| // options/variants via the `--config` options override. |
| // |
| // There is ongoing discussion about how to move forward here: |
| // https://github.com/rust-lang/rustfmt/pull/5379 |
| // |
| // For now, do not validate whether the option or value is stable, |
| // just always set it. |
| self.$i.1 = true; |
| self.$i.2 = option_value; |
| } |
| )+ |
| _ => panic!("Unknown config key in override: {}", key) |
| } |
| |
| match key { |
| "max_width" |
| | "use_small_heuristics" |
| | "fn_call_width" |
| | "single_line_if_else_max_width" |
| | "single_line_let_else_max_width" |
| | "attr_fn_like_width" |
| | "struct_lit_width" |
| | "struct_variant_width" |
| | "array_width" |
| | "chain_width" => self.set_heuristics(), |
| "merge_imports" => self.set_merge_imports(), |
| "fn_args_layout" => self.set_fn_args_layout(), |
| &_ => (), |
| } |
| } |
| |
| #[allow(unreachable_pub)] |
| pub fn is_hidden_option(name: &str) -> bool { |
| const HIDE_OPTIONS: [&str; 6] = [ |
| "verbose", |
| "verbose_diff", |
| "file_lines", |
| "width_heuristics", |
| "merge_imports", |
| "fn_args_layout" |
| ]; |
| HIDE_OPTIONS.contains(&name) |
| } |
| |
| #[allow(unreachable_pub)] |
| pub fn print_docs(out: &mut dyn Write, include_unstable: bool) { |
| use std::cmp; |
| let max = 0; |
| $( let max = cmp::max(max, stringify!($i).len()+1); )+ |
| let space_str = " ".repeat(max); |
| writeln!(out, "Configuration Options:").unwrap(); |
| $( |
| if $stb || include_unstable { |
| let name_raw = stringify!($i); |
| |
| if !Config::is_hidden_option(name_raw) { |
| let mut name_out = String::with_capacity(max); |
| for _ in name_raw.len()..max-1 { |
| name_out.push(' ') |
| } |
| name_out.push_str(name_raw); |
| name_out.push(' '); |
| let mut default_str = format!("{}", $def); |
| if default_str.is_empty() { |
| default_str = String::from("\"\""); |
| } |
| writeln!(out, |
| "{}{} Default: {}{}", |
| name_out, |
| <$ty>::doc_hint(), |
| default_str, |
| if !$stb { " (unstable)" } else { "" }).unwrap(); |
| $( |
| writeln!(out, "{}{}", space_str, $dstring).unwrap(); |
| )+ |
| writeln!(out).unwrap(); |
| } |
| } |
| )+ |
| } |
| |
| fn set_width_heuristics(&mut self, heuristics: WidthHeuristics) { |
| let max_width = self.max_width.2; |
| let get_width_value = | |
| was_set: bool, |
| override_value: usize, |
| heuristic_value: usize, |
| config_key: &str, |
| | -> usize { |
| if !was_set { |
| return heuristic_value; |
| } |
| if override_value > max_width { |
| eprintln!( |
| "`{0}` cannot have a value that exceeds `max_width`. \ |
| `{0}` will be set to the same value as `max_width`", |
| config_key, |
| ); |
| return max_width; |
| } |
| override_value |
| }; |
| |
| let fn_call_width = get_width_value( |
| self.was_set().fn_call_width(), |
| self.fn_call_width.2, |
| heuristics.fn_call_width, |
| "fn_call_width", |
| ); |
| self.fn_call_width.2 = fn_call_width; |
| |
| let attr_fn_like_width = get_width_value( |
| self.was_set().attr_fn_like_width(), |
| self.attr_fn_like_width.2, |
| heuristics.attr_fn_like_width, |
| "attr_fn_like_width", |
| ); |
| self.attr_fn_like_width.2 = attr_fn_like_width; |
| |
| let struct_lit_width = get_width_value( |
| self.was_set().struct_lit_width(), |
| self.struct_lit_width.2, |
| heuristics.struct_lit_width, |
| "struct_lit_width", |
| ); |
| self.struct_lit_width.2 = struct_lit_width; |
| |
| let struct_variant_width = get_width_value( |
| self.was_set().struct_variant_width(), |
| self.struct_variant_width.2, |
| heuristics.struct_variant_width, |
| "struct_variant_width", |
| ); |
| self.struct_variant_width.2 = struct_variant_width; |
| |
| let array_width = get_width_value( |
| self.was_set().array_width(), |
| self.array_width.2, |
| heuristics.array_width, |
| "array_width", |
| ); |
| self.array_width.2 = array_width; |
| |
| let chain_width = get_width_value( |
| self.was_set().chain_width(), |
| self.chain_width.2, |
| heuristics.chain_width, |
| "chain_width", |
| ); |
| self.chain_width.2 = chain_width; |
| |
| let single_line_if_else_max_width = get_width_value( |
| self.was_set().single_line_if_else_max_width(), |
| self.single_line_if_else_max_width.2, |
| heuristics.single_line_if_else_max_width, |
| "single_line_if_else_max_width", |
| ); |
| self.single_line_if_else_max_width.2 = single_line_if_else_max_width; |
| |
| let single_line_let_else_max_width = get_width_value( |
| self.was_set().single_line_let_else_max_width(), |
| self.single_line_let_else_max_width.2, |
| heuristics.single_line_let_else_max_width, |
| "single_line_let_else_max_width", |
| ); |
| self.single_line_let_else_max_width.2 = single_line_let_else_max_width; |
| } |
| |
| fn set_heuristics(&mut self) { |
| let max_width = self.max_width.2; |
| match self.use_small_heuristics.2 { |
| Heuristics::Default => |
| self.set_width_heuristics(WidthHeuristics::scaled(max_width)), |
| Heuristics::Max => self.set_width_heuristics(WidthHeuristics::set(max_width)), |
| Heuristics::Off => self.set_width_heuristics(WidthHeuristics::null()), |
| }; |
| } |
| |
| fn set_ignore(&mut self, dir: &Path) { |
| self.ignore.2.add_prefix(dir); |
| } |
| |
| fn set_merge_imports(&mut self) { |
| if self.was_set().merge_imports() { |
| eprintln!( |
| "Warning: the `merge_imports` option is deprecated. \ |
| Use `imports_granularity=\"Crate\"` instead" |
| ); |
| if !self.was_set().imports_granularity() { |
| self.imports_granularity.2 = if self.merge_imports() { |
| ImportGranularity::Crate |
| } else { |
| ImportGranularity::Preserve |
| }; |
| } |
| } |
| } |
| |
| fn set_fn_args_layout(&mut self) { |
| if self.was_set().fn_args_layout() { |
| eprintln!( |
| "Warning: the `fn_args_layout` option is deprecated. \ |
| Use `fn_params_layout`. instead" |
| ); |
| if !self.was_set().fn_params_layout() { |
| self.fn_params_layout.2 = self.fn_args_layout(); |
| } |
| } |
| } |
| |
| #[allow(unreachable_pub)] |
| /// Returns `true` if the config key was explicitly set and is the default value. |
| pub fn is_default(&self, key: &str) -> bool { |
| $( |
| if let stringify!($i) = key { |
| return self.$i.1 && self.$i.2 == $def; |
| } |
| )+ |
| false |
| } |
| } |
| |
| // Template for the default configuration |
| impl Default for Config { |
| fn default() -> Config { |
| Config { |
| $( |
| $i: (Cell::new(false), false, $def, $stb), |
| )+ |
| } |
| } |
| } |
| ) |
| } |
| |
| pub(crate) fn is_stable_option_and_value<T>( |
| option_name: &str, |
| option_stable: bool, |
| option_value: &T, |
| ) -> bool |
| where |
| T: PartialEq + std::fmt::Debug + ConfigType, |
| { |
| let nightly = crate::is_nightly_channel!(); |
| let variant_stable = option_value.stable_variant(); |
| match (nightly, option_stable, variant_stable) { |
| // Stable with an unstable option |
| (false, false, _) => { |
| eprintln!( |
| "Warning: can't set `{option_name} = {option_value:?}`, unstable features are only \ |
| available in nightly channel." |
| ); |
| false |
| } |
| // Stable with a stable option, but an unstable variant |
| (false, true, false) => { |
| eprintln!( |
| "Warning: can't set `{option_name} = {option_value:?}`, unstable variants are only \ |
| available in nightly channel." |
| ); |
| false |
| } |
| // Nightly: everything allowed |
| // Stable with stable option and variant: allowed |
| (true, _, _) | (false, true, true) => true, |
| } |
| } |