diff --git a/Configurations.md b/Configurations.md index ac638ff91e6..122d5f04c5b 100644 --- a/Configurations.md +++ b/Configurations.md @@ -2593,6 +2593,20 @@ By default this option is set as a percentage of [`max_width`](#max_width) provi See also [`max_width`](#max_width) and [`use_small_heuristics`](#use_small_heuristics) +## `style_edition` + +The style edition used to format your code. rustfmt output may differ between style editions. + +- **Default value**: `"2015"` +- **Possible values**: `"2015"`, `"2018"`, `"2021"`, `"2024"` +- **Stable**: Yes + +The `style_edition` can be specified using the `--style-edition` CLI option or via your `rustfmt.toml` + +```toml +style_edition = "2021" +``` + ## `tab_spaces` Number of spaces per tab diff --git a/Contributing.md b/Contributing.md index b986a887c92..ec57748de76 100644 --- a/Contributing.md +++ b/Contributing.md @@ -259,3 +259,73 @@ the config struct and parse a config file, etc. Checking an option is done by accessing the correct field on the config struct, e.g., `config.max_width()`. Most functions have a `Config`, or one can be accessed via a visitor or context of some kind. + +###### Adding `style_edition` support for your new config + +`style_edition` is the mechanism used by rustfmt to maintain backwards compatability, while allowing +room to change formatting over time. All configuration options must implement the `StyleEditionDefault` triat. +If you're definig a new config you can use the `#[style_edition]` attribute to help you automatically define the trait. + +When defining a new configuration option it should be sufficient to only define the default value which applies to +all `style_edtions`. + +For simple cases you can define a new unit struct that defines the `style_edition` defaults for your new +configuration option. + +```rust +#[style_edition(100)] +struct MaxWidth; +``` + +If your new config is an enum you can set the defulats similarly to how you'd set it for a unit struct. + +```rust +/// Controls how rustfmt should handle case in hexadecimal literals. +#[style_edition(HexLiteralCase::Preserve)] +#[config_type] +pub enum HexLiteralCase { + /// Leave the literal as-is + Preserve, + /// Ensure all literals use uppercase lettering + Upper, + /// Ensure all literals use lowercase lettering + Lower, +} +``` + +You can alternatively set the default directly on the enum variant itself. + +```rust +/// Client-preference for coloured output. +#[style_edition] +#[config_type] +pub enum Color { + /// Always use color, whether it is a piped or terminal output + Always, + /// Never use color + Never, + #[se_default] + /// Automatically use color, if supported by terminal + Auto, +} +``` + +In cases where your default value is a more complex type you'll have to implement `StyleEditionDefault` by hand. + +```rust +/// A set of directories, files and modules that rustfmt should ignore. +#[derive(Default, Clone, Debug, PartialEq)] +pub struct IgnoreList { + /// A set of path specified in rustfmt.toml. + path_set: HashSet, + /// A path to rustfmt.toml. + rustfmt_toml_path: PathBuf, +} + +impl StyleEditionDefault for IgnoreList { + type ConfigType = Self; + fn style_edition_default(_style_edition: StyleEdition) -> Self::ConfigType { + IgnoreList::default() + } +} +``` diff --git a/config_proc_macro/src/attrs.rs b/config_proc_macro/src/attrs.rs index d8de9aae088..273d6cd9c10 100644 --- a/config_proc_macro/src/attrs.rs +++ b/config_proc_macro/src/attrs.rs @@ -57,7 +57,7 @@ fn is_attr_name_value(attr: &syn::Attribute, name: &str) -> bool { } } -fn is_attr_path(attr: &syn::Attribute, name: &str) -> bool { +pub fn is_attr_path(attr: &syn::Attribute, name: &str) -> bool { match &attr.meta { syn::Meta::Path(path) if path.is_ident(name) => true, _ => false, diff --git a/config_proc_macro/src/lib.rs b/config_proc_macro/src/lib.rs index 0c54c132c97..619e102f501 100644 --- a/config_proc_macro/src/lib.rs +++ b/config_proc_macro/src/lib.rs @@ -6,6 +6,7 @@ mod attrs; mod config_type; mod item_enum; mod item_struct; +mod style_edition; mod utils; use std::str::FromStr; @@ -82,3 +83,14 @@ pub fn rustfmt_only_ci_test(_args: TokenStream, input: TokenStream) -> TokenStre token_stream } } + +/// Implement the StyleEditionDefault trait for the given item; +#[proc_macro_attribute] +pub fn style_edition(args: TokenStream, input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as syn::Item); + let args = parse_macro_input!(args as style_edition::StyleEditionDefault); + + let output = style_edition::define_style_edition(args, input).unwrap(); + let result = TokenStream::from(output); + result +} diff --git a/config_proc_macro/src/style_edition.rs b/config_proc_macro/src/style_edition.rs new file mode 100644 index 00000000000..06b2c47c601 --- /dev/null +++ b/config_proc_macro/src/style_edition.rs @@ -0,0 +1,355 @@ +use proc_macro2::{Span, TokenStream}; +use quote::{quote, ToTokens}; +use syn::parse::{Parse, ParseStream}; +use syn::spanned::Spanned; +use syn::Token; + +use crate::attrs; + +/// Returns `true` if the given attribute configures the deafult StyleEdition value +pub fn se_default(attr: &syn::Attribute) -> bool { + attrs::is_attr_path(attr, "se_default") +} + +/// Returns `true` if the given attribute configures the deafult value for StyleEdition2015 +pub fn se_2015(attr: &syn::Attribute) -> bool { + attrs::is_attr_path(attr, "se_2015") +} + +/// Returns `true` if the given attribute configures the deafult value for StyleEdition2018 +pub fn se_2018(attr: &syn::Attribute) -> bool { + attrs::is_attr_path(attr, "se_2018") +} + +/// Returns `true` if the given attribute configures the deafult value for StyleEdition2021 +pub fn se_2021(attr: &syn::Attribute) -> bool { + attrs::is_attr_path(attr, "se_2021") +} + +/// Returns `true` if the given attribute configures the deafult for StyleEdition2024 +pub fn se_2024(attr: &syn::Attribute) -> bool { + attrs::is_attr_path(attr, "se_2024") +} + +/// Defines `style_edition` on enum or struct. +pub fn define_style_edition( + defaults: StyleEditionDefault, + item: syn::Item, +) -> syn::Result { + match item { + syn::Item::Struct(st) => define_style_edition_struct(defaults, st), + syn::Item::Enum(en) => define_style_edition_enum(defaults, en), + _ => panic!("Expected enum or struct"), + } +} + +pub struct StyleEditionDefault { + default: Option, + se2015: Option, + se2018: Option, + se2021: Option, + se2024: Option, +} + +impl StyleEditionDefault { + /// a sinlge default for all style editions + fn single_default(&self) -> bool { + self.default.is_some() + && self.se2015.is_none() + && self.se2018.is_none() + && self.se2021.is_none() + && self.se2024.is_none() + } + /// Infer the type from the default value + fn ty_from_default(&self) -> syn::Result { + match &self.default { + Some(syn::Expr::Lit(lit)) => match lit.lit { + syn::Lit::Bool(_) => { + return Ok(syn::TypePath { + qself: None, + path: path_from_str("bool"), + } + .into()); + } + syn::Lit::Int(_) => { + return Ok(syn::TypePath { + qself: None, + path: path_from_str("usize"), + } + .into()); + } + _ => {} + }, + _ => {} + } + Err(syn::parse::Error::new( + Span::call_site(), + "could not determine type from default value", + )) + } + + fn enum_expr_path(varient: &syn::Variant, en: &syn::ItemEnum) -> syn::ExprPath { + let mut path = path_from_ident(&en.ident); + path.segments.push(varient.ident.clone().into()); + syn::ExprPath { + attrs: vec![], + qself: None, + path, + } + } + + /// Set the style edition based on the the annotated attribute + /// For example: + /// ```ignore + /// #[style_edition] + /// enum Example { + /// #[se_default] // <-- Default style edition + /// A, + /// #[se_2018] // <-- Explicit override for StypeEdition2018 + /// B, + /// } + /// ``` + fn set_defaults_by_enum_variant_attr(&mut self, en: &syn::ItemEnum) { + for varient in en.variants.iter() { + for attr in varient.attrs.iter() { + if se_default(attr) { + self.default.replace(Self::enum_expr_path(varient, en).into()); + break; + } else if se_2015(attr) { + self.se2015.replace(Self::enum_expr_path(varient, en).into()); + break; + } else if se_2018(attr) { + self.se2018.replace(Self::enum_expr_path(varient, en).into()); + break; + } else if se_2021(attr) { + self.se2021.replace(Self::enum_expr_path(varient, en).into()); + break; + } else if se_2024(attr) { + self.se2024.replace(Self::enum_expr_path(varient, en).into()); + break; + } + } + } + } + + /// Set the style edition based on the lhs of the assignment in the attribute + /// e.g. `#[style_edition(true, se_2015=false)]` + fn set_by_assignment(&mut self, assignment: &syn::ExprAssign) -> syn::Result<()> { + match assignment.left.as_ref() { + syn::Expr::Path(expr) => { + let se2015 = syn::Ident::new("se_2015", Span::call_site()); + let se2018 = syn::Ident::new("se_2018", Span::call_site()); + let se2021 = syn::Ident::new("se_2021", Span::call_site()); + let se2024 = syn::Ident::new("se_2024", Span::call_site()); + let ident = expr + .path + .segments + .first() + .map(|segment| segment.ident.clone()) + .expect("should be at least one ident"); + + if ident == se2015 { + self.se2015.replace(*assignment.right.clone()); + return Ok(()); + } else if ident == se2018 { + self.se2018.replace(*assignment.right.clone()); + return Ok(()); + } else if ident == se2021 { + self.se2021.replace(*assignment.right.clone()); + return Ok(()); + } else if ident == se2024 { + self.se2024.replace(*assignment.right.clone()); + return Ok(()); + } + } + _ => {} + } + Err(syn::Error::new( + Span::call_site(), + format!( + "Unknown lhs {:?}", + assignment.left.as_ref().to_token_stream().to_string() + ), + )) + } + + fn quote(&self, name: &syn::Ident) -> TokenStream { + let default = self.default.as_ref(); + let se2015 = self.se2015.as_ref().map(|expr| { + quote! { + if #name == crate::config::StyleEdition::Edition2015 { + return #expr; + } + } + }); + let se2018 = self.se2018.as_ref().map(|expr| { + quote! { + if #name == crate::config::StyleEdition::Edition2018 { + return #expr; + } + } + }); + let se2021 = self.se2021.as_ref().map(|expr| { + quote! { + if #name == crate::config::StyleEdition::Edition2021 { + return #expr; + } + } + }); + let se2024 = self.se2024.as_ref().map(|expr| { + quote! { + if #name == crate::config::StyleEdition::Edition2024 { + return #expr; + } + } + }); + quote! { + #se2015 + #se2018 + #se2021 + #se2024 + #default + } + } +} + +fn path_from_str(s: &str) -> syn::Path { + syn::Path::from(syn::Ident::new(s, Span::call_site())) +} + +fn path_from_ident(ident: &syn::Ident) -> syn::Path { + syn::Path::from(ident.clone()) +} + +impl Default for StyleEditionDefault { + fn default() -> Self { + Self { + default: None, + se2015: None, + se2018: None, + se2021: None, + se2024: None, + } + } +} + +/// Parse StyleEdition values from attribute macro. +/// For example: `#[style_edition(100)]`, which sets the defaul to 100 for all style edtions +/// or `#[style_edition(false, se_2024=true)]`, which sets the default for all style editions except +/// `StyleEdition2024` to false, and explicitly sets `StyleEdition2024=true` +impl Parse for StyleEditionDefault { + fn parse(input: ParseStream) -> syn::Result { + let mut se_default = StyleEditionDefault::default(); + if input.is_empty() { + return Ok(se_default); + } + let defaults = input.parse_terminated(syn::Expr::parse, Token![,])?; + for (idx, pair) in defaults.into_pairs().enumerate() { + let expr = pair.into_value(); + match &expr { + syn::Expr::Assign(assign) => { + if idx == 0 { + se_default.default.replace(*assign.right.to_owned()); + continue; + } + se_default.set_by_assignment(assign)?; + } + syn::Expr::Lit(_) if idx == 0 => { + se_default.default.replace(expr); + } + syn::Expr::Path(_) if idx == 0 => { + se_default.default.replace(expr); + } + _ => { + return Err(syn::parse::Error::new( + expr.span(), + format!( + "Can't create a style edition default from the expr: {:?}", + expr.to_token_stream().to_string() + ), + )); + } + } + } + Ok(se_default) + } +} + +fn define_style_edition_struct( + defaults: StyleEditionDefault, + st: syn::ItemStruct, +) -> syn::Result { + let ty = defaults.ty_from_default()?; + let ident = st.ident.clone(); + define_style_edition_inner(defaults, ty, ident, st.into()) +} + +fn define_style_edition_enum( + mut defaults: StyleEditionDefault, + mut en: syn::ItemEnum, +) -> syn::Result { + let ty = syn::TypePath { + qself: None, + path: syn::Path::from(en.ident.clone()), + }; + + let ident = en.ident.clone(); + defaults.set_defaults_by_enum_variant_attr(&en); + for mut variant in en.variants.iter_mut() { + remove_style_edition_attrs(&mut variant); + } + define_style_edition_inner(defaults, ty.into(), ident, en.into()) +} + +/// Remove attributes specific to `style_edition` from enum variant fields. +/// These attributes are only used as markers to help us generate `StyleEditionDefault` +/// trait implementations. They should be removed to avoid compilation errors. +fn remove_style_edition_attrs(variant: &mut syn::Variant) { + let metas = variant + .attrs + .iter() + .filter(|attr| { + !se_default(attr) + && !se_2015(attr) + && !se_2018(attr) + && !se_2021(attr) + && !se_2024(attr) + }) + .cloned() + .collect(); + + variant.attrs = metas; +} + +fn define_style_edition_inner( + defaults: StyleEditionDefault, + ty: syn::Type, + ident: syn::Ident, + item: syn::Item, +) -> syn::Result { + if defaults.default.is_none() { + return Err(syn::Error::new( + Span::call_site(), + format!("Missing default style edition value for {:?}", ident), + )); + } + + let name = if defaults.single_default() { + syn::Ident::new("_se", Span::call_site()) + } else { + syn::Ident::new("style_edition", Span::call_site()) + }; + + let value = defaults.quote(&name); + + Ok(quote! { + #item + + impl crate::config::StyleEditionDefault for #ident { + type ConfigType = #ty; + fn style_edition_default(#name: crate::config::StyleEdition) -> Self::ConfigType { + #value + } + } + }) +} diff --git a/src/bin/main.rs b/src/bin/main.rs index 8f5d980f561..3c73dea44f0 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -16,7 +16,7 @@ use getopts::{Matches, Options}; use crate::rustfmt::{ load_config, CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName, - FormatReportFormatterBuilder, Input, Session, Verbosity, + FormatReportFormatterBuilder, Input, Session, StyleEdition, Verbosity, }; fn main() { @@ -117,6 +117,14 @@ fn make_opts() -> Options { found reverts to the input file path", "[Path for the configuration file]", ); + opts.optopt( + "", + "style-edition", + "Rustfmt style edition used to format your code. If specified, this style edition will \ + take precedence over the value configured in any `rustfmt.toml`. rustfmt output may \ + differ between style editions.", + "[2015|2018|2021|2024]", + ); opts.optopt("", "edition", "Rust edition to use", "[2015|2018|2021]"); opts.optopt( "", @@ -195,6 +203,7 @@ fn is_nightly() -> bool { fn execute(opts: &Options) -> Result { let matches = opts.parse(env::args().skip(1))?; let options = GetOptsOptions::from_matches(&matches)?; + let style_edition = options.style_edition; match determine_operation(&matches)? { Operation::Help(HelpOp::None) => { @@ -202,7 +211,8 @@ fn execute(opts: &Options) -> Result { Ok(0) } Operation::Help(HelpOp::Config) => { - Config::print_docs(&mut stdout(), options.unstable_features); + let config = style_edition.unwrap_or_default().config(); + config.print_docs(&mut stdout(), options.unstable_features); Ok(0) } Operation::Help(HelpOp::FileLines) => { @@ -214,7 +224,11 @@ fn execute(opts: &Options) -> Result { Ok(0) } Operation::ConfigOutputDefault { path } => { - let toml = Config::default().all_options().to_toml()?; + let toml = style_edition + .unwrap_or_default() + .config() + .all_options() + .to_toml()?; if let Some(path) = path { let mut file = File::create(path)?; file.write_all(toml.as_bytes())?; @@ -232,7 +246,8 @@ fn execute(opts: &Options) -> Result { let file = PathBuf::from(path); let file = file.canonicalize().unwrap_or(file); - let (config, _) = load_config(Some(file.parent().unwrap()), Some(options))?; + let (config, _) = + load_config(style_edition, Some(file.parent().unwrap()), Some(options))?; let toml = config.all_options().to_toml()?; io::stdout().write_all(toml.as_bytes())?; @@ -247,8 +262,9 @@ fn execute(opts: &Options) -> Result { } fn format_string(input: String, options: GetOptsOptions) -> Result { + let style_edition = options.style_edition; // try to read config from local directory - let (mut config, _) = load_config(Some(Path::new(".")), Some(options.clone()))?; + let (mut config, _) = load_config(style_edition, Some(Path::new(".")), Some(options.clone()))?; if options.check { config.set().emit_mode(EmitMode::Diff); @@ -294,7 +310,8 @@ fn format( options: &GetOptsOptions, ) -> Result { options.verify_file_lines(&files); - let (config, config_path) = load_config(None, Some(options.clone()))?; + let style_edition = options.style_edition; + let (config, config_path) = load_config(style_edition, None, Some(options.clone()))?; if config.verbose() == Verbosity::Verbose { if let Some(path) = config_path.as_ref() { @@ -315,8 +332,11 @@ fn format( } else { // Check the file directory if the config-path could not be read or not provided if config_path.is_none() { - let (local_config, config_path) = - load_config(Some(file.parent().unwrap()), Some(options.clone()))?; + let (local_config, config_path) = load_config( + style_edition, + Some(file.parent().unwrap()), + Some(options.clone()), + )?; if local_config.verbose() == Verbosity::Verbose { if let Some(path) = config_path { println!( @@ -510,6 +530,7 @@ struct GetOptsOptions { backup: bool, check: bool, edition: Option, + style_edition: Option, color: Option, file_lines: FileLines, // Default is all lines in all files. unstable_features: bool, @@ -601,6 +622,10 @@ impl GetOptsOptions { options.edition = Some(edition_from_edition_str(edition_str)?); } + if let Some(ref style_edition) = matches.opt_str("style-edition") { + options.style_edition = Some(style_edition_from_edition_str(style_edition)?); + } + if matches.opt_present("backup") { options.backup = true; } @@ -698,6 +723,16 @@ fn edition_from_edition_str(edition_str: &str) -> Result { } } +fn style_edition_from_edition_str(edition_str: &str) -> Result { + match edition_str { + "2015" => Ok(StyleEdition::Edition2015), + "2018" => Ok(StyleEdition::Edition2018), + "2021" => Ok(StyleEdition::Edition2021), + "2024" => Ok(StyleEdition::Edition2024), + _ => Err(format_err!("Invalid value for `--style-edition`")), + } +} + fn emit_mode_from_emit_str(emit_str: &str) -> Result { match emit_str { "files" => Ok(EmitMode::Files), diff --git a/src/config/config_type.rs b/src/config/config_type.rs index 54ca7676dfc..d205ae4c930 100644 --- a/src/config/config_type.rs +++ b/src/config/config_type.rs @@ -1,6 +1,6 @@ use crate::config::file_lines::FileLines; use crate::config::macro_names::MacroSelectors; -use crate::config::options::{IgnoreList, WidthHeuristics}; +use crate::config::options::{IgnoreList, StyleEdition, WidthHeuristics}; /// Trait for types that can be used in `Config`. pub(crate) trait ConfigType: Sized { @@ -17,6 +17,12 @@ pub(crate) trait ConfigType: Sized { } } +/// Defines the default value for the given style edition +pub(crate) trait StyleEditionDefault { + type ConfigType; + fn style_edition_default(style_edition: StyleEdition) -> Self::ConfigType; +} + impl ConfigType for bool { fn doc_hint() -> String { String::from("") @@ -73,7 +79,7 @@ macro_rules! create_config { // - $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 ),+ );+ $(;)*) => ( + ($($i:ident: $ty:ty, $def:ty, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => ( #[cfg(test)] use std::collections::HashSet; use std::io::Write; @@ -149,6 +155,23 @@ macro_rules! create_config { } impl Config { + #[allow(unreachable_pub)] + pub fn deafult_with_style_edition(style_edition: StyleEdition) -> Config { + + Config { + $( + $i: ( + Cell::new(false), + false, + <$def as StyleEditionDefault>::style_edition_default( + style_edition + ), + $stb + ), + )+ + } + } + $( #[allow(unreachable_pub)] pub fn $i(&self) -> $ty { @@ -294,7 +317,7 @@ macro_rules! create_config { } #[allow(unreachable_pub)] - pub fn print_docs(out: &mut dyn Write, include_unstable: bool) { + pub fn print_docs(&self, out: &mut dyn Write, include_unstable: bool) { use std::cmp; let max = 0; $( let max = cmp::max(max, stringify!($i).len()+1); )+ @@ -311,7 +334,10 @@ macro_rules! create_config { } name_out.push_str(name_raw); name_out.push(' '); - let mut default_str = format!("{}", $def); + let default = <$def as StyleEditionDefault>::style_edition_default( + self.style_edition.2 + ); + let mut default_str = format!("{}", default); if default_str.is_empty() { default_str = String::from("\"\""); } @@ -456,7 +482,10 @@ macro_rules! create_config { pub fn is_default(&self, key: &str) -> bool { $( if let stringify!($i) = key { - return self.$i.1 && self.$i.2 == $def; + let default = <$def as StyleEditionDefault>::style_edition_default( + self.style_edition.2 + ); + return self.$i.1 && self.$i.2 == default; } )+ false @@ -466,11 +495,7 @@ macro_rules! create_config { // Template for the default configuration impl Default for Config { fn default() -> Config { - Config { - $( - $i: (Cell::new(false), false, $def, $stb), - )+ - } + Config::deafult_with_style_edition(StyleEdition::default()) } } ) diff --git a/src/config/file_lines.rs b/src/config/file_lines.rs index e4e51a3f3b4..2bb063f4487 100644 --- a/src/config/file_lines.rs +++ b/src/config/file_lines.rs @@ -11,6 +11,8 @@ use serde::{ser, Deserialize, Deserializer, Serialize, Serializer}; use serde_json as json; use thiserror::Error; +use crate::config::StyleEditionDefault; + /// A range of lines in a file, inclusive of both ends. pub struct LineRange { pub(crate) file: Lrc, @@ -156,6 +158,13 @@ impl Range { #[derive(Clone, Debug, Default, PartialEq)] pub struct FileLines(Option>>); +impl StyleEditionDefault for FileLines { + type ConfigType = Self; + fn style_edition_default(_style_edition: crate::StyleEdition) -> Self::ConfigType { + FileLines::all() + } +} + impl fmt::Display for FileLines { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.0 { diff --git a/src/config/lists.rs b/src/config/lists.rs index 11cb17068fb..5efed4738db 100644 --- a/src/config/lists.rs +++ b/src/config/lists.rs @@ -1,6 +1,6 @@ //! Configuration options related to rewriting a list. -use rustfmt_config_proc_macro::config_type; +use rustfmt_config_proc_macro::{config_type, style_edition}; use crate::config::IndentStyle; @@ -26,6 +26,7 @@ impl DefinitiveListTactic { /// Formatting tactic for lists. This will be cast down to a /// `DefinitiveListTactic` depending on the number and length of the items and /// their comments. +#[style_edition(ListTactic::Mixed)] #[config_type] pub enum ListTactic { /// One item per row. @@ -40,6 +41,7 @@ pub enum ListTactic { Mixed, } +#[style_edition(SeparatorTactic::Vertical)] #[config_type] pub enum SeparatorTactic { Always, @@ -58,6 +60,7 @@ impl SeparatorTactic { } /// Where to put separator. +#[style_edition(SeparatorPlace::Front)] #[config_type] pub enum SeparatorPlace { Front, diff --git a/src/config/macro_names.rs b/src/config/macro_names.rs index 26ad78d6dca..a8e1b2f1001 100644 --- a/src/config/macro_names.rs +++ b/src/config/macro_names.rs @@ -7,6 +7,8 @@ use serde::{Deserialize, Serialize}; use serde_json as json; use thiserror::Error; +use super::config_type::StyleEditionDefault; + /// Defines the name of a macro. #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)] pub struct MacroName(String); @@ -60,6 +62,13 @@ impl str::FromStr for MacroSelector { #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct MacroSelectors(pub Vec); +impl StyleEditionDefault for MacroSelectors { + type ConfigType = Self; + fn style_edition_default(_style_edition: crate::StyleEdition) -> Self::ConfigType { + Self::default() + } +} + impl fmt::Display for MacroSelectors { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0.iter().format(", ")) diff --git a/src/config/mod.rs b/src/config/mod.rs index 14f27f3f8b6..94c148bd599 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -7,7 +7,7 @@ use std::{env, fs}; use thiserror::Error; -use crate::config::config_type::ConfigType; +use crate::config::config_type::{ConfigType, StyleEditionDefault}; #[allow(unreachable_pub)] pub use crate::config::file_lines::{FileLines, FileName, Range}; #[allow(unreachable_pub)] @@ -34,155 +34,166 @@ pub(crate) mod macro_names; // `name: value type, default value, is stable, description;` create_config! { // Fundamental stuff - max_width: usize, 100, true, "Maximum width of each line"; - hard_tabs: bool, false, true, "Use tab characters for indentation, spaces for alignment"; - tab_spaces: usize, 4, true, "Number of spaces per tab"; - newline_style: NewlineStyle, NewlineStyle::Auto, true, "Unix or Windows line endings"; - indent_style: IndentStyle, IndentStyle::Block, false, "How do we indent expressions or items"; + style_edition: StyleEdition, StyleEdition, true, "The style edition being used"; + max_width: usize, MaxWidth, true, "Maximum width of each line"; + hard_tabs: bool, HardTabs, true, "Use tab characters for indentation, spaces for alignment"; + tab_spaces: usize, TabSpaces, true, "Number of spaces per tab"; + newline_style: NewlineStyle, NewlineStyle, true, "Unix or Windows line endings"; + indent_style: IndentStyle, IndentStyle, false, "How do we indent expressions or items"; // Width Heuristics - use_small_heuristics: Heuristics, Heuristics::Default, true, "Whether to use different \ + use_small_heuristics: Heuristics, Heuristics, true, "Whether to use different \ formatting for items and expressions if they satisfy a heuristic notion of 'small'"; - width_heuristics: WidthHeuristics, WidthHeuristics::scaled(100), false, + width_heuristics: WidthHeuristics, WidthHeuristics, false, "'small' heuristic values"; - fn_call_width: usize, 60, true, "Maximum width of the args of a function call before \ + fn_call_width: usize, FnCallWidth, true, "Maximum width of the args of a function call before \ falling back to vertical formatting."; - attr_fn_like_width: usize, 70, true, "Maximum width of the args of a function-like \ - attributes before falling back to vertical formatting."; - struct_lit_width: usize, 18, true, "Maximum width in the body of a struct lit before \ - falling back to vertical formatting."; - struct_variant_width: usize, 35, true, "Maximum width in the body of a struct variant before \ - falling back to vertical formatting."; - array_width: usize, 60, true, "Maximum width of an array literal before falling \ + attr_fn_like_width: usize, AttrFnLikeWidth, true, "Maximum width of the args of a \ + function-like attributes before falling back to vertical formatting."; + struct_lit_width: usize, StructLitWidth, true, "Maximum width in the body of a struct lit \ + before falling back to vertical formatting."; + struct_variant_width: usize, StructVariantWidth, true, "Maximum width in the body of a struct \ + variant before falling back to vertical formatting."; + array_width: usize, ArrayWidth, true, "Maximum width of an array literal before falling \ back to vertical formatting."; - chain_width: usize, 60, true, "Maximum length of a chain to fit on a single line."; - single_line_if_else_max_width: usize, 50, true, "Maximum line length for single line if-else \ - expressions. A value of zero means always break if-else expressions."; + chain_width: usize, ChainWidth, true, "Maximum length of a chain to fit on a single line."; + single_line_if_else_max_width: usize, SingleLineIfElseMaxWidth, true, "Maximum line length for \ + single line if-else expressions. A value of zero means always break if-else expressions."; // Comments. macros, and strings - wrap_comments: bool, false, false, "Break comments to fit on the line"; - format_code_in_doc_comments: bool, false, false, "Format the code snippet in doc comments."; - doc_comment_code_block_width: usize, 100, false, "Maximum width for code snippets in doc \ - comments. No effect unless format_code_in_doc_comments = true"; - comment_width: usize, 80, false, + wrap_comments: bool, WrapComments, false, "Break comments to fit on the line"; + format_code_in_doc_comments: bool, FormatCodeInDocComments, false, + "Format the code snippet in doc comments."; + doc_comment_code_block_width: usize, DocCommentCodeBlockWidth, false, "Maximum width for code \ + snippets in doc comments. No effect unless format_code_in_doc_comments = true"; + comment_width: usize, CommentWidth, false, "Maximum length of comments. No effect unless wrap_comments = true"; - normalize_comments: bool, false, false, "Convert /* */ comments to // comments where possible"; - normalize_doc_attributes: bool, false, false, "Normalize doc attributes as doc comments"; - format_strings: bool, false, false, "Format string literals where necessary"; - format_macro_matchers: bool, false, false, + normalize_comments: bool, NormalizeComments, false, + "Convert /* */ comments to // comments here possible"; + normalize_doc_attributes: bool, NormalizeDocAttributes, false, + "Normalize doc attributes as doc comments"; + format_strings: bool, FormatStrings, false, "Format string literals where necessary"; + format_macro_matchers: bool, FormatMacroMatchers, false, "Format the metavariable matching patterns in macros"; - format_macro_bodies: bool, true, false, "Format the bodies of macros"; - skip_macro_invocations: MacroSelectors, MacroSelectors::default(), false, + format_macro_bodies: bool, FormatMacroBodies, false, "Format the bodies of macros"; + skip_macro_invocations: MacroSelectors, MacroSelectors, false, "Skip formatting the bodies of macros invoked with the following names."; - hex_literal_case: HexLiteralCase, HexLiteralCase::Preserve, false, + hex_literal_case: HexLiteralCase, HexLiteralCase, false, "Format hexadecimal integer literals"; // Single line expressions and items - empty_item_single_line: bool, true, false, + empty_item_single_line: bool, EmptyItemSingleLine, false, "Put empty-body functions and impls on a single line"; - struct_lit_single_line: bool, true, false, + struct_lit_single_line: bool, StructLitSingleLine, false, "Put small struct literals on a single line"; - fn_single_line: bool, false, false, "Put single-expression functions on a single line"; - where_single_line: bool, false, false, "Force where-clauses to be on a single line"; + fn_single_line: bool, FnSingleLine, false, "Put single-expression functions on a single line"; + where_single_line: bool, WhereSingleLine, false, "Force where-clauses to be on a single line"; // Imports - imports_indent: IndentStyle, IndentStyle::Block, false, "Indent of imports"; - imports_layout: ListTactic, ListTactic::Mixed, false, "Item layout inside a import block"; - imports_granularity: ImportGranularity, ImportGranularity::Preserve, false, + imports_indent: IndentStyle, IndentStyle, false, "Indent of imports"; + imports_layout: ListTactic, ListTactic, false, "Item layout inside a import block"; + imports_granularity: ImportGranularity, ImportGranularity, false, "Merge or split imports to the provided granularity"; - group_imports: GroupImportsTactic, GroupImportsTactic::Preserve, false, + group_imports: GroupImportsTactic, GroupImportsTactic, false, "Controls the strategy for how imports are grouped together"; - merge_imports: bool, false, false, "(deprecated: use imports_granularity instead)"; + merge_imports: bool, MergeImports, false, "(deprecated: use imports_granularity instead)"; // Ordering - reorder_imports: bool, true, true, "Reorder import and extern crate statements alphabetically"; - reorder_modules: bool, true, true, "Reorder module statements alphabetically in group"; - reorder_impl_items: bool, false, false, "Reorder impl items"; + reorder_imports: bool, ReorderImports, true, "Reorder import and extern crate statements \ + alphabetically"; + reorder_modules: bool, ReorderModules, true, + "Reorder module statements alphabetically in group"; + reorder_impl_items: bool, ReorderImplItems, false, "Reorder impl items"; // Spaces around punctuation - type_punctuation_density: TypeDensity, TypeDensity::Wide, false, + type_punctuation_density: TypeDensity, TypeDensity, false, "Determines if '+' or '=' are wrapped in spaces in the punctuation of types"; - space_before_colon: bool, false, false, "Leave a space before the colon"; - space_after_colon: bool, true, false, "Leave a space after the colon"; - spaces_around_ranges: bool, false, false, "Put spaces around the .. and ..= range operators"; - binop_separator: SeparatorPlace, SeparatorPlace::Front, false, + space_before_colon: bool, SpaceBeforeColon, false, "Leave a space before the colon"; + space_after_colon: bool, SpaceAfterColon, false, "Leave a space after the colon"; + spaces_around_ranges: bool, SpacesAroundRanges, false, + "Put spaces around the .. and ..= range operators"; + binop_separator: SeparatorPlace, SeparatorPlace, false, "Where to put a binary operator when a binary expression goes multiline"; // Misc. - remove_nested_parens: bool, true, true, "Remove nested parens"; - combine_control_expr: bool, true, false, "Combine control expressions with function calls"; - short_array_element_width_threshold: usize, 10, true, + remove_nested_parens: bool, RemoveNestedParens, true, "Remove nested parens"; + combine_control_expr: bool, CombineControlExpr, false, + "Combine control expressions with function calls"; + short_array_element_width_threshold: usize, ShortArrayElementWidthThreshold, true, "Width threshold for an array element to be considered short"; - overflow_delimited_expr: bool, false, false, + overflow_delimited_expr: bool, OverflowDelimitedExpr, false, "Allow trailing bracket/brace delimited expressions to overflow"; - struct_field_align_threshold: usize, 0, false, + struct_field_align_threshold: usize, StructFieldAlignThreshold, false, "Align struct fields if their diffs fits within threshold"; - enum_discrim_align_threshold: usize, 0, false, + enum_discrim_align_threshold: usize, EnumDiscrimAlignThreshold, false, "Align enum variants discrims, if their diffs fit within threshold"; - match_arm_blocks: bool, true, false, "Wrap the body of arms in blocks when it does not fit on \ - the same line with the pattern of arms"; - match_arm_leading_pipes: MatchArmLeadingPipe, MatchArmLeadingPipe::Never, true, + match_arm_blocks: bool, MatchArmBlocks, false, "Wrap the body of arms in blocks when it does \ + not fit on the same line with the pattern of arms"; + match_arm_leading_pipes: MatchArmLeadingPipe, MatchArmLeadingPipe, true, "Determines whether leading pipes are emitted on match arms"; - force_multiline_blocks: bool, false, false, + force_multiline_blocks: bool, ForceMultilineBlocks, false, "Force multiline closure bodies and match arms to be wrapped in a block"; - fn_args_layout: Density, Density::Tall, true, + fn_args_layout: Density, Density, true, "(deprecated: use fn_params_layout instead)"; - fn_params_layout: Density, Density::Tall, true, + fn_params_layout: Density, Density, true, "Control the layout of parameters in function signatures."; - brace_style: BraceStyle, BraceStyle::SameLineWhere, false, "Brace style for items"; - control_brace_style: ControlBraceStyle, ControlBraceStyle::AlwaysSameLine, false, + brace_style: BraceStyle, BraceStyle, false, "Brace style for items"; + control_brace_style: ControlBraceStyle, ControlBraceStyle, false, "Brace style for control flow constructs"; - trailing_semicolon: bool, true, false, + trailing_semicolon: bool, TrailingSemicolon, false, "Add trailing semicolon after break, continue and return"; - trailing_comma: SeparatorTactic, SeparatorTactic::Vertical, false, + trailing_comma: SeparatorTactic, SeparatorTactic, false, "How to handle trailing commas for lists"; - match_block_trailing_comma: bool, false, true, + match_block_trailing_comma: bool, MatchBlockTrailingComma, true, "Put a trailing comma after a block based match arm (non-block arms are not affected)"; - blank_lines_upper_bound: usize, 1, false, + blank_lines_upper_bound: usize, BlankLinesUpperBound, false, "Maximum number of blank lines which can be put between items"; - blank_lines_lower_bound: usize, 0, false, + blank_lines_lower_bound: usize, BlankLinesLowerBound, false, "Minimum number of blank lines which must be put between items"; - edition: Edition, Edition::Edition2015, true, "The edition of the parser (RFC 2052)"; - version: Version, Version::One, false, "Version of formatting rules"; - inline_attribute_width: usize, 0, false, + edition: Edition, Edition, true, "The edition of the parser (RFC 2052)"; + version: Version, Version, false, "Version of formatting rules"; + inline_attribute_width: usize, InlineAttributeWidth, false, "Write an item and its attribute on the same line \ if their combined width is below a threshold"; - format_generated_files: bool, true, false, "Format generated files"; + format_generated_files: bool, FormatGeneratedFiles, false, "Format generated files"; // Options that can change the source code beyond whitespace/blocks (somewhat linty things) - merge_derives: bool, true, true, "Merge multiple `#[derive(...)]` into a single one"; - use_try_shorthand: bool, false, true, "Replace uses of the try! macro by the ? shorthand"; - use_field_init_shorthand: bool, false, true, "Use field initialization shorthand if possible"; - force_explicit_abi: bool, true, true, "Always print the abi for extern items"; - condense_wildcard_suffixes: bool, false, false, "Replace strings of _ wildcards by a single .. \ - in tuple patterns"; + merge_derives: bool, MergeDerives, true, "Merge multiple `#[derive(...)]` into a single one"; + use_try_shorthand: bool, UseTryShorthand, true, + "Replace uses of the try! macro by the ? shorthand"; + use_field_init_shorthand: bool, UseFieldInitShorthand, true, + "Use field initialization shorthand if possible"; + force_explicit_abi: bool, ForceExplicitAbi, true, "Always print the abi for extern items"; + condense_wildcard_suffixes: bool, CondenseWildcardSuffixes, false, + "Replace strings of _ wildcards by a single .. in tuple patterns"; // Control options (changes the operation of rustfmt, rather than the formatting) - color: Color, Color::Auto, false, + color: Color, Color, false, "What Color option to use when none is supplied: Always, Never, Auto"; - required_version: String, env!("CARGO_PKG_VERSION").to_owned(), false, + required_version: String, RequiredVersion, false, "Require a specific version of rustfmt"; - unstable_features: bool, false, false, + unstable_features: bool, UnstableFeatures, false, "Enables unstable features. Only available on nightly channel"; - disable_all_formatting: bool, false, true, "Don't reformat anything"; - skip_children: bool, false, false, "Don't reformat out of line modules"; - hide_parse_errors: bool, false, false, "Hide errors from the parser"; - error_on_line_overflow: bool, false, false, "Error if unable to get all lines within max_width"; - error_on_unformatted: bool, false, false, + disable_all_formatting: bool, DisableAllFormatting, true, "Don't reformat anything"; + skip_children: bool, SkipChildren, false, "Don't reformat out of line modules"; + hide_parse_errors: bool, HideParseErrors, false, "Hide errors from the parser"; + error_on_line_overflow: bool, ErrorOnLineOverflow, false, + "Error if unable to get all lines within max_width"; + error_on_unformatted: bool, ErrorOnUnformatted, false, "Error if unable to get comments or string literals within max_width, \ or they are left with trailing whitespaces"; - ignore: IgnoreList, IgnoreList::default(), false, + ignore: IgnoreList, IgnoreList, false, "Skip formatting the specified files and directories"; // Not user-facing - verbose: Verbosity, Verbosity::Normal, false, "How much to information to emit to the user"; - file_lines: FileLines, FileLines::all(), false, + verbose: Verbosity, Verbosity, false, "How much to information to emit to the user"; + file_lines: FileLines, FileLines, false, "Lines to format; this is not supported in rustfmt.toml, and can only be specified \ via the --file-lines option"; - emit_mode: EmitMode, EmitMode::Files, false, + emit_mode: EmitMode, EmitMode, false, "What emit Mode to use when none is supplied"; - make_backup: bool, false, false, "Backup changed files"; - print_misformatted_file_names: bool, false, true, + make_backup: bool, MakeBackup, false, "Backup changed files"; + print_misformatted_file_names: bool, PrintMisformattedFileNames, true, "Prints the names of mismatched files that were formatted. Prints the names of \ files that would be formatted when used with `--check` mode. "; } @@ -230,11 +241,14 @@ impl Config { /// /// Returns a `Config` if the config could be read and parsed from /// the file, otherwise errors. - pub(super) fn from_toml_path(file_path: &Path) -> Result { + pub(super) fn from_toml_path( + style_edtion: Option, + file_path: &Path, + ) -> Result { let mut file = File::open(&file_path)?; let mut toml = String::new(); file.read_to_string(&mut toml)?; - Config::from_toml(&toml, file_path.parent().unwrap()) + Config::from_toml(style_edtion, &toml, file_path.parent().unwrap()) .map_err(|err| Error::new(ErrorKind::InvalidData, err)) } @@ -247,7 +261,10 @@ impl Config { /// /// Returns the `Config` to use, and the path of the project file if there was /// one. - pub(super) fn from_resolved_toml_path(dir: &Path) -> Result<(Config, Option), Error> { + pub(super) fn from_resolved_toml_path( + style_edition: Option, + dir: &Path, + ) -> Result<(Config, Option), Error> { /// Try to find a project file in the given directory and its parents. /// Returns the path of a the nearest project file if one exists, /// or `None` if no project file was found. @@ -293,11 +310,17 @@ impl Config { match resolve_project_file(dir)? { None => Ok((Config::default(), None)), - Some(path) => Config::from_toml_path(&path).map(|config| (config, Some(path))), + Some(path) => { + Config::from_toml_path(style_edition, &path).map(|config| (config, Some(path))) + } } } - pub(crate) fn from_toml(toml: &str, dir: &Path) -> Result { + pub(crate) fn from_toml( + style_edition: Option, + toml: &str, + dir: &Path, + ) -> Result { let parsed: ::toml::Value = toml .parse() .map_err(|e| format!("Could not parse TOML: {}", e))?; @@ -311,12 +334,22 @@ impl Config { err.push_str(msg) } } - match parsed.try_into() { - Ok(parsed_config) => { + + match parsed.try_into::() { + Ok(mut parsed_config) => { if !err.is_empty() { eprint!("{}", err); } - Ok(Config::default().fill_from_parsed_config(parsed_config, dir)) + let style_edition_from_config = parsed_config.style_edition.take(); + // Set the default config based on the style edition if provided as an argument, + // otherwise use the style_edition specified in the toml file. If no style edition + // was found fallback to the default. + let style_edition = style_edition + .or(style_edition_from_config) + .unwrap_or_default(); + Ok(style_edition + .config() + .fill_from_parsed_config(parsed_config, dir)) } Err(e) => { err.push_str("Error: Decoding config file failed:\n"); @@ -331,6 +364,7 @@ impl Config { /// Loads a config by checking the client-supplied options and if appropriate, the /// file system (including searching the file system for overrides). pub fn load_config( + style_edtion: Option, file_path: Option<&Path>, options: Option, ) -> Result<(Config, Option), Error> { @@ -340,11 +374,15 @@ pub fn load_config( }; let result = if let Some(over_ride) = over_ride { - Config::from_toml_path(over_ride.as_ref()).map(|p| (p, Some(over_ride.to_owned()))) + Config::from_toml_path(style_edtion, over_ride.as_ref()) + .map(|p| (p, Some(over_ride.to_owned()))) } else if let Some(file_path) = file_path { - Config::from_resolved_toml_path(file_path) + Config::from_resolved_toml_path(style_edtion, file_path) } else { - Ok((Config::default(), None)) + Ok(( + Config::deafult_with_style_edition(style_edtion.unwrap_or_default()), + None, + )) }; result.map(|(mut c, p)| { @@ -419,8 +457,9 @@ mod test { #[allow(dead_code)] mod mock { use super::super::*; - use rustfmt_config_proc_macro::config_type; + use rustfmt_config_proc_macro::{config_type, style_edition}; + #[style_edition(PartiallyUnstableOption::V1)] #[config_type] pub(crate) enum PartiallyUnstableOption { V1, @@ -429,55 +468,65 @@ mod test { V3, } + #[style_edition(false)] + pub(crate) struct StableOption; + + #[style_edition(false)] + pub(crate) struct UnstableOption; + create_config! { // Options that are used by the generated functions - max_width: usize, 100, true, "Maximum width of each line"; - required_version: String, env!("CARGO_PKG_VERSION").to_owned(), false, + style_edition: StyleEdition, StyleEdition, true, "The style edition being used"; + max_width: usize, MaxWidth, true, "Maximum width of each line"; + required_version: String, RequiredVersion, false, "Require a specific version of rustfmt."; - ignore: IgnoreList, IgnoreList::default(), false, + ignore: IgnoreList, IgnoreList, false, "Skip formatting the specified files and directories."; - verbose: Verbosity, Verbosity::Normal, false, + verbose: Verbosity, Verbosity, false, "How much to information to emit to the user"; - file_lines: FileLines, FileLines::all(), false, + file_lines: FileLines, FileLines, false, "Lines to format; this is not supported in rustfmt.toml, and can only be specified \ via the --file-lines option"; // merge_imports deprecation - imports_granularity: ImportGranularity, ImportGranularity::Preserve, false, + imports_granularity: ImportGranularity, ImportGranularity, false, "Merge imports"; - merge_imports: bool, false, false, "(deprecated: use imports_granularity instead)"; + merge_imports: bool, MergeImports, false, + "(deprecated: use imports_granularity instead)"; // fn_args_layout renamed to fn_params_layout - fn_args_layout: Density, Density::Tall, true, + fn_args_layout: Density, Density, true, "(deprecated: use fn_params_layout instead)"; - fn_params_layout: Density, Density::Tall, true, + fn_params_layout: Density, Density, true, "Control the layout of parameters in a function signatures."; // Width Heuristics - use_small_heuristics: Heuristics, Heuristics::Default, true, + use_small_heuristics: Heuristics, Heuristics, true, "Whether to use different formatting for items and \ expressions if they satisfy a heuristic notion of 'small'."; - width_heuristics: WidthHeuristics, WidthHeuristics::scaled(100), false, + width_heuristics: WidthHeuristics, WidthHeuristics, false, "'small' heuristic values"; - fn_call_width: usize, 60, true, "Maximum width of the args of a function call before \ - falling back to vertical formatting."; - attr_fn_like_width: usize, 70, true, "Maximum width of the args of a function-like \ - attributes before falling back to vertical formatting."; - struct_lit_width: usize, 18, true, "Maximum width in the body of a struct lit before \ + fn_call_width: usize, FnCallWidth, true, "Maximum width of the args of a function call \ + before falling back to vertical formatting."; + attr_fn_like_width: usize, AttrFnLikeWidth, true, "Maximum width of the args of a \ + function-like attributes before falling back to vertical formatting."; + struct_lit_width: usize, StructLitWidth, true, "Maximum width in the body of a struct \ + lit before falling back to vertical formatting."; + struct_variant_width: usize, StructVariantWidth, true, "Maximum width in the body of a \ + struct variant before falling back to vertical formatting."; + array_width: usize, ArrayWidth, true, "Maximum width of an array literal before \ falling back to vertical formatting."; - struct_variant_width: usize, 35, true, "Maximum width in the body of a struct \ - variant before falling back to vertical formatting."; - array_width: usize, 60, true, "Maximum width of an array literal before falling \ - back to vertical formatting."; - chain_width: usize, 60, true, "Maximum length of a chain to fit on a single line."; - single_line_if_else_max_width: usize, 50, true, "Maximum line length for single \ - line if-else expressions. A value of zero means always break if-else expressions."; + chain_width: usize, ChainWidth, true, + "Maximum length of a chain to fit on a single line."; + single_line_if_else_max_width: usize, SingleLineIfElseMaxWidth, true, + "Maximum line length for single line if-else expressions. A value of zero means \ + always break if-else expressions."; // Options that are used by the tests - stable_option: bool, false, true, "A stable option"; - unstable_option: bool, false, false, "An unstable option"; - partially_unstable_option: PartiallyUnstableOption, PartiallyUnstableOption::V1, true, + stable_option: bool, StableOption, true, "A stable option"; + unstable_option: bool, UnstableOption, false, "An unstable option"; + partially_unstable_option: PartiallyUnstableOption, PartiallyUnstableOption, true, "A partially unstable option"; } @@ -537,6 +586,161 @@ mod test { } } + #[allow(unreachable_pub)] + mod style_edition_configs { + use crate::config::StyleEdition; + use crate::config::{ConfigType, StyleEditionDefault}; + use rustfmt_config_proc_macro::style_edition; + + #[test] + fn test_impl_default_style_edition_struct_for_all_editions() { + #[style_edition(100)] + #[derive(Debug, PartialEq)] + struct Unit; + + // regardless of the style edition used the value will always return 100 + assert_eq!(Unit::style_edition_default(StyleEdition::Edition2015), 100); + assert_eq!(Unit::style_edition_default(StyleEdition::Edition2018), 100); + assert_eq!(Unit::style_edition_default(StyleEdition::Edition2021), 100); + assert_eq!(Unit::style_edition_default(StyleEdition::Edition2024), 100); + } + + #[test] + fn test_impl_style_edition_struct_for_multiple_editions() { + #[style_edition(100, se_2015 = 99, se_2024 = 115)] + struct Unit2; + + impl ConfigType for Unit2 { + fn doc_hint() -> String { + String::new() + } + } + // regardless of the style edition used the value will always return 100 + assert_eq!(Unit2::style_edition_default(StyleEdition::Edition2015), 99); + assert_eq!(Unit2::style_edition_default(StyleEdition::Edition2018), 100); + assert_eq!(Unit2::style_edition_default(StyleEdition::Edition2021), 100); + assert_eq!(Unit2::style_edition_default(StyleEdition::Edition2024), 115); + } + + #[test] + #[allow(dead_code)] + fn test_impl_style_edition_enum_for_all_editions() { + #[style_edition(Color::Blue)] + #[derive(PartialEq, Debug)] + enum Color { + Red, + Gree, + Blue, + } + assert_eq!( + Color::style_edition_default(StyleEdition::Edition2015), + Color::Blue + ); + assert_eq!( + Color::style_edition_default(StyleEdition::Edition2018), + Color::Blue + ); + assert_eq!( + Color::style_edition_default(StyleEdition::Edition2021), + Color::Blue + ); + assert_eq!( + Color::style_edition_default(StyleEdition::Edition2024), + Color::Blue + ); + } + + #[test] + #[allow(dead_code)] + fn test_impl_style_edition_enum_for_multiple_editions() { + #[style_edition(Color::Blue, se_2024=Color::Red, se_2021=Color::Green)] + #[derive(Debug, PartialEq)] + enum Color { + Red, + Green, + Blue, + } + + assert_eq!( + Color::style_edition_default(StyleEdition::Edition2015), + Color::Blue + ); + assert_eq!( + Color::style_edition_default(StyleEdition::Edition2018), + Color::Blue + ); + assert_eq!( + Color::style_edition_default(StyleEdition::Edition2021), + Color::Green + ); + assert_eq!( + Color::style_edition_default(StyleEdition::Edition2024), + Color::Red + ); + } + + #[test] + #[ignore] + #[allow(dead_code)] + fn test_impl_style_edition_enum_for_all_editions_using_variant_attribute() { + #[style_edition] + #[derive(Debug, PartialEq)] + enum Color { + Red, + #[se_default] + Green, + Blue, + } + assert_eq!( + Color::style_edition_default(StyleEdition::Edition2015), + Color::Green + ); + assert_eq!( + Color::style_edition_default(StyleEdition::Edition2018), + Color::Green + ); + assert_eq!( + Color::style_edition_default(StyleEdition::Edition2021), + Color::Green + ); + assert_eq!( + Color::style_edition_default(StyleEdition::Edition2024), + Color::Green + ); + } + + #[test] + #[allow(dead_code)] + fn test_impl_style_edition_enum_for_multiple_editions_using_variant_attribute() { + #[style_edition] + #[derive(Debug, PartialEq)] + enum Color { + #[se_2024] + Red, + #[se_default] + Green, + #[se_2018] + Blue, + } + assert_eq!( + Color::style_edition_default(StyleEdition::Edition2015), + Color::Green + ); + assert_eq!( + Color::style_edition_default(StyleEdition::Edition2018), + Color::Blue + ); + assert_eq!( + Color::style_edition_default(StyleEdition::Edition2021), + Color::Green + ); + assert_eq!( + Color::style_edition_default(StyleEdition::Edition2024), + Color::Red + ); + } + } + #[test] fn test_config_set() { let mut config = Config::default(); @@ -566,7 +770,7 @@ mod test { #[test] fn test_was_set() { - let config = Config::from_toml("hard_tabs = true", Path::new("")).unwrap(); + let config = Config::from_toml(None, "hard_tabs = true", Path::new("")).unwrap(); assert_eq!(config.was_set().hard_tabs(), true); assert_eq!(config.was_set().verbose(), false); @@ -582,7 +786,8 @@ mod test { use self::mock::Config; let mut output = Vec::new(); - Config::print_docs(&mut output, false); + let config = Config::default(); + config.print_docs(&mut output, false); let s = str::from_utf8(&output).unwrap(); assert_eq!(s.contains(PRINT_DOCS_STABLE_OPTION), true); @@ -595,7 +800,8 @@ mod test { use self::mock::Config; let mut output = Vec::new(); - Config::print_docs(&mut output, true); + let config = Config::default(); + config.print_docs(&mut output, true); let s = str::from_utf8(&output).unwrap(); assert_eq!(s.contains(PRINT_DOCS_STABLE_OPTION), true); @@ -603,10 +809,10 @@ mod test { assert_eq!(s.contains(PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION), true); } - #[test] - fn test_dump_default_config() { - let default_config = format!( - r#"max_width = 100 + fn se_2015_through_se_2021_default_config(style_edition: StyleEdition) -> String { + format!( + r#"style_edition = "{}" +max_width = 100 hard_tabs = false tab_spaces = 4 newline_style = "Auto" @@ -684,9 +890,42 @@ ignore = [] emit_mode = "Files" make_backup = false "#, + style_edition, env!("CARGO_PKG_VERSION") - ); + ) + } + + #[test] + fn test_dump_default_config() { + let style_edition = StyleEdition::Edition2015; + let default_config = se_2015_through_se_2021_default_config(style_edition); let toml = Config::default().all_options().to_toml().unwrap(); + // ensure that Config::default() uses StyleEdition::Edition2015 + assert_eq!(&toml, &default_config); + } + + #[test] + fn test_dump_default_config_se_2015_through_2021() { + use StyleEdition::*; + for style_edtion in [Edition2015, Edition2018, Edition2021] { + let default_config = se_2015_through_se_2021_default_config(style_edtion); + let toml = Config::deafult_with_style_edition(style_edtion) + .all_options() + .to_toml() + .unwrap(); + assert_eq!(&toml, &default_config); + } + } + + #[test] + fn test_dump_default_config_se_2024() { + // TODO(ytmimi): Once we start altering the defaults for se_2024 update the string + let style_ediition = StyleEdition::Edition2024; + let default_config = se_2015_through_se_2021_default_config(style_ediition); + let toml = Config::deafult_with_style_edition(StyleEdition::Edition2024) + .all_options() + .to_toml() + .unwrap(); assert_eq!(&toml, &default_config); } @@ -714,7 +953,7 @@ make_backup = false #[nightly_only_test] #[test] fn test_unstable_from_toml() { - let config = Config::from_toml("unstable_features = true", Path::new("")).unwrap(); + let config = Config::from_toml(None, "unstable_features = true", Path::new("")).unwrap(); assert_eq!(config.was_set().unstable_features(), true); assert_eq!(config.unstable_features(), true); } @@ -730,7 +969,7 @@ make_backup = false unstable_features = true merge_imports = true "#; - let config = Config::from_toml(toml, Path::new("")).unwrap(); + let config = Config::from_toml(None, toml, Path::new("")).unwrap(); assert_eq!(config.imports_granularity(), ImportGranularity::Crate); } @@ -742,7 +981,7 @@ make_backup = false merge_imports = true imports_granularity = "Preserve" "#; - let config = Config::from_toml(toml, Path::new("")).unwrap(); + let config = Config::from_toml(None, toml, Path::new("")).unwrap(); assert_eq!(config.imports_granularity(), ImportGranularity::Preserve); } @@ -753,7 +992,7 @@ make_backup = false unstable_features = true merge_imports = true "#; - let mut config = Config::from_toml(toml, Path::new("")).unwrap(); + let mut config = Config::from_toml(None, toml, Path::new("")).unwrap(); config.override_value("imports_granularity", "Preserve"); assert_eq!(config.imports_granularity(), ImportGranularity::Preserve); } @@ -765,7 +1004,7 @@ make_backup = false unstable_features = true imports_granularity = "Module" "#; - let mut config = Config::from_toml(toml, Path::new("")).unwrap(); + let mut config = Config::from_toml(None, toml, Path::new("")).unwrap(); config.override_value("merge_imports", "true"); // no effect: the new option always takes precedence assert_eq!(config.imports_granularity(), ImportGranularity::Module); @@ -782,7 +1021,7 @@ make_backup = false use_small_heuristics = "Default" max_width = 200 "#; - let config = Config::from_toml(toml, Path::new("")).unwrap(); + let config = Config::from_toml(None, toml, Path::new("")).unwrap(); assert_eq!(config.array_width(), 120); assert_eq!(config.attr_fn_like_width(), 140); assert_eq!(config.chain_width(), 120); @@ -798,7 +1037,7 @@ make_backup = false use_small_heuristics = "Max" max_width = 120 "#; - let config = Config::from_toml(toml, Path::new("")).unwrap(); + let config = Config::from_toml(None, toml, Path::new("")).unwrap(); assert_eq!(config.array_width(), 120); assert_eq!(config.attr_fn_like_width(), 120); assert_eq!(config.chain_width(), 120); @@ -814,7 +1053,7 @@ make_backup = false use_small_heuristics = "Off" max_width = 100 "#; - let config = Config::from_toml(toml, Path::new("")).unwrap(); + let config = Config::from_toml(None, toml, Path::new("")).unwrap(); assert_eq!(config.array_width(), usize::max_value()); assert_eq!(config.attr_fn_like_width(), usize::max_value()); assert_eq!(config.chain_width(), usize::max_value()); @@ -836,7 +1075,7 @@ make_backup = false struct_lit_width = 30 struct_variant_width = 34 "#; - let config = Config::from_toml(toml, Path::new("")).unwrap(); + let config = Config::from_toml(None, toml, Path::new("")).unwrap(); assert_eq!(config.array_width(), 20); assert_eq!(config.attr_fn_like_width(), 40); assert_eq!(config.chain_width(), 20); @@ -858,7 +1097,7 @@ make_backup = false struct_lit_width = 30 struct_variant_width = 34 "#; - let config = Config::from_toml(toml, Path::new("")).unwrap(); + let config = Config::from_toml(None, toml, Path::new("")).unwrap(); assert_eq!(config.array_width(), 20); assert_eq!(config.attr_fn_like_width(), 40); assert_eq!(config.chain_width(), 20); @@ -880,7 +1119,7 @@ make_backup = false struct_lit_width = 30 struct_variant_width = 34 "#; - let config = Config::from_toml(toml, Path::new("")).unwrap(); + let config = Config::from_toml(None, toml, Path::new("")).unwrap(); assert_eq!(config.array_width(), 20); assert_eq!(config.attr_fn_like_width(), 40); assert_eq!(config.chain_width(), 20); @@ -896,7 +1135,7 @@ make_backup = false max_width = 90 fn_call_width = 95 "#; - let config = Config::from_toml(toml, Path::new("")).unwrap(); + let config = Config::from_toml(None, toml, Path::new("")).unwrap(); assert_eq!(config.fn_call_width(), 90); } @@ -906,7 +1145,7 @@ make_backup = false max_width = 80 attr_fn_like_width = 90 "#; - let config = Config::from_toml(toml, Path::new("")).unwrap(); + let config = Config::from_toml(None, toml, Path::new("")).unwrap(); assert_eq!(config.attr_fn_like_width(), 80); } @@ -916,7 +1155,7 @@ make_backup = false max_width = 78 struct_lit_width = 90 "#; - let config = Config::from_toml(toml, Path::new("")).unwrap(); + let config = Config::from_toml(None, toml, Path::new("")).unwrap(); assert_eq!(config.struct_lit_width(), 78); } @@ -926,7 +1165,7 @@ make_backup = false max_width = 80 struct_variant_width = 90 "#; - let config = Config::from_toml(toml, Path::new("")).unwrap(); + let config = Config::from_toml(None, toml, Path::new("")).unwrap(); assert_eq!(config.struct_variant_width(), 80); } @@ -936,7 +1175,7 @@ make_backup = false max_width = 60 array_width = 80 "#; - let config = Config::from_toml(toml, Path::new("")).unwrap(); + let config = Config::from_toml(None, toml, Path::new("")).unwrap(); assert_eq!(config.array_width(), 60); } @@ -946,7 +1185,7 @@ make_backup = false max_width = 80 chain_width = 90 "#; - let config = Config::from_toml(toml, Path::new("")).unwrap(); + let config = Config::from_toml(None, toml, Path::new("")).unwrap(); assert_eq!(config.chain_width(), 80); } @@ -956,7 +1195,7 @@ make_backup = false max_width = 70 single_line_if_else_max_width = 90 "#; - let config = Config::from_toml(toml, Path::new("")).unwrap(); + let config = Config::from_toml(None, toml, Path::new("")).unwrap(); assert_eq!(config.single_line_if_else_max_width(), 70); } diff --git a/src/config/options.rs b/src/config/options.rs index 408017d2432..fbf9a915351 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -4,7 +4,7 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use itertools::Itertools; -use rustfmt_config_proc_macro::config_type; +use rustfmt_config_proc_macro::{config_type, style_edition}; use serde::de::{SeqAccess, Visitor}; use serde::ser::SerializeSeq; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -12,6 +12,82 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::config::lists::*; use crate::config::Config; +use super::config_type::StyleEditionDefault; + +macro_rules! style_edition_default { + ($name:ident, $value:tt) => { + #[style_edition($value)] + pub struct $name; + }; + ($($name:ident, $value:tt);* $(;)*) => { + $( + style_edition_default!($name, $value); + )* + }; +} + +macro_rules! style_edition_always_false { + ($($name:ident),* $(,)*) => { + $( + style_edition_default!($name, false); + )* + }; +} + +macro_rules! style_edition_always_true { + ($($name:ident),* $(,)*) => { + $( + style_edition_default!($name, true); + )* + }; +} + +style_edition_default! { + MaxWidth, 100; + TabSpaces, 4; + FnCallWidth, 60; + AttrFnLikeWidth, 70; + StructLitWidth, 18; + StructVariantWidth, 35; + ArrayWidth, 60; + ChainWidth, 60; + SingleLineIfElseMaxWidth, 50; + DocCommentCodeBlockWidth, 100; + CommentWidth, 80; + ShortArrayElementWidthThreshold, 10; + StructFieldAlignThreshold, 0; + EnumDiscrimAlignThreshold, 0; + BlankLinesUpperBound, 1; + BlankLinesLowerBound, 0; + InlineAttributeWidth, 0; +} + +style_edition_always_false! { + HardTabs, WrapComments, FormatCodeInDocComments, NormalizeComments, NormalizeDocAttributes, + FormatStrings, FormatMacroMatchers, FnSingleLine, WhereSingleLine, MergeImports, + ReorderImplItems, SpaceBeforeColon, SpacesAroundRanges, OverflowDelimitedExpr, + ForceMultilineBlocks, MatchBlockTrailingComma, UseTryShorthand, UseFieldInitShorthand, + CondenseWildcardSuffixes, UnstableFeatures, DisableAllFormatting, SkipChildren, + HideParseErrors, ErrorOnLineOverflow, ErrorOnUnformatted, MakeBackup, + PrintMisformattedFileNames, +} + +style_edition_always_true! { + FormatMacroBodies, EmptyItemSingleLine, StructLitSingleLine, ReorderImports, ReorderModules, + SpaceAfterColon, RemoveNestedParens, CombineControlExpr, MatchArmBlocks, TrailingSemicolon, + FormatGeneratedFiles, MergeDerives, ForceExplicitAbi, +} + +pub struct RequiredVersion; + +impl StyleEditionDefault for RequiredVersion { + type ConfigType = String; + fn style_edition_default(_style_edition: StyleEdition) -> Self::ConfigType { + env!("CARGO_PKG_VERSION").to_owned() + } +} + +#[style_edition(NewlineStyle::Auto)] #[config_type] pub enum NewlineStyle { /// Auto-detect based on the raw source input. @@ -24,6 +100,7 @@ pub enum NewlineStyle { Native, } +#[style_edition(BraceStyle::SameLineWhere)] #[config_type] /// Where to put the opening brace of items (`fn`, `impl`, etc.). pub enum BraceStyle { @@ -36,6 +113,7 @@ pub enum BraceStyle { SameLineWhere, } +#[style_edition(ControlBraceStyle::AlwaysSameLine)] #[config_type] /// Where to put the opening brace of conditional expressions (`if`, `match`, etc.). pub enum ControlBraceStyle { @@ -47,6 +125,7 @@ pub enum ControlBraceStyle { AlwaysNextLine, } +#[style_edition] #[config_type] /// How to indent. pub enum IndentStyle { @@ -54,9 +133,11 @@ pub enum IndentStyle { /// the first line. Visual, /// First line is on a new line and all lines align with **block** indent. + #[se_default] Block, } +#[style_edition(Density::Tall)] #[config_type] /// How to place a list-like items. /// FIXME: Issue-3581: this should be renamed to ItemsLayout when publishing 2.0 @@ -69,6 +150,7 @@ pub enum Density { Vertical, } +#[style_edition(TypeDensity::Wide)] #[config_type] /// Spacing around type combinators. pub enum TypeDensity { @@ -78,6 +160,7 @@ pub enum TypeDensity { Wide, } +#[style_edition(Heuristics::Default)] #[config_type] /// Heuristic settings that can be used to simply /// the configuration of the granular width configurations @@ -102,6 +185,7 @@ impl Density { } } +#[style_edition(GroupImportsTactic::Preserve)] #[config_type] /// Configuration for import groups, i.e. sets of imports separated by newlines. pub enum GroupImportsTactic { @@ -116,6 +200,7 @@ pub enum GroupImportsTactic { One, } +#[style_edition(ImportGranularity::Preserve)] #[config_type] /// How to merge imports. pub enum ImportGranularity { @@ -132,6 +217,7 @@ pub enum ImportGranularity { } /// Controls how rustfmt should handle case in hexadecimal literals. +#[style_edition(HexLiteralCase::Preserve)] #[config_type] pub enum HexLiteralCase { /// Leave the literal as-is @@ -151,6 +237,7 @@ pub enum ReportTactic { /// What Rustfmt should emit. Mostly corresponds to the `--emit` command line /// option. +#[style_edition(EmitMode::Files)] #[config_type] pub enum EmitMode { /// Emits to files. @@ -174,6 +261,7 @@ pub enum EmitMode { } /// Client-preference for coloured output. +#[style_edition(Color::Auto)] #[config_type] pub enum Color { /// Always use color, whether it is a piped or terminal output @@ -184,6 +272,7 @@ pub enum Color { Auto, } +#[style_edition(Version::One)] #[config_type] /// rustfmt format style version. pub enum Version { @@ -204,6 +293,7 @@ impl Color { } /// How chatty should Rustfmt be? +#[style_edition(Verbosity::Normal)] #[config_type] pub enum Verbosity { /// Emit more. @@ -292,6 +382,13 @@ impl WidthHeuristics { } } +impl StyleEditionDefault for WidthHeuristics { + type ConfigType = Self; + fn style_edition_default(_style_edition: StyleEdition) -> Self::ConfigType { + WidthHeuristics::scaled(100) + } +} + impl ::std::str::FromStr for WidthHeuristics { type Err = &'static str; @@ -315,6 +412,13 @@ pub struct IgnoreList { rustfmt_toml_path: PathBuf, } +impl StyleEditionDefault for IgnoreList { + type ConfigType = Self; + fn style_edition_default(_style_edition: StyleEdition) -> Self::ConfigType { + IgnoreList::default() + } +} + impl fmt::Display for IgnoreList { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( @@ -409,6 +513,7 @@ pub trait CliOptions { } /// The edition of the syntax and semntics of code (RFC 2052). +#[style_edition(Edition::Edition2015)] #[config_type] pub enum Edition { #[value = "2015"] @@ -453,6 +558,7 @@ impl PartialOrd for Edition { } /// Controls how rustfmt should handle leading pipes on match arms. +#[style_edition(MatchArmLeadingPipe::Never)] #[config_type] pub enum MatchArmLeadingPipe { /// Place leading pipes on all match arms @@ -462,3 +568,50 @@ pub enum MatchArmLeadingPipe { /// Preserve any existing leading pipes Preserve, } + +/// Defines the default values for each config according to [the style guide]. +/// rustfmt output may differ between style editions. +/// +/// [the style guide]: link TBD +#[config_type] +pub enum StyleEdition { + #[value = "2015"] + #[doc_hint = "2015"] + /// [Edition 2015]. + /// [Edition 2015]: link TBD + Edition2015, + #[value = "2018"] + #[doc_hint = "2018"] + /// [Edition 2018]. + /// [Edition 2018]: link TBD + Edition2018, + #[value = "2021"] + #[doc_hint = "2021"] + /// [Edition 2021]. + /// [Edition 2021]: link TBD + Edition2021, + #[value = "2024"] + #[doc_hint = "2024"] + /// [Edition 2024]. + /// [Edition 2024]: link TBD + Edition2024, +} + +impl StyleEditionDefault for StyleEdition { + type ConfigType = Self; + fn style_edition_default(style_edition: StyleEdition) -> Self::ConfigType { + style_edition + } +} + +impl Default for StyleEdition { + fn default() -> Self { + Self::Edition2015 + } +} + +impl StyleEdition { + pub fn config(self) -> Config { + Config::deafult_with_style_edition(self) + } +} diff --git a/src/git-rustfmt/main.rs b/src/git-rustfmt/main.rs index 579778edbe7..e9f202b2b41 100644 --- a/src/git-rustfmt/main.rs +++ b/src/git-rustfmt/main.rs @@ -58,7 +58,7 @@ fn get_files(input: &str) -> Vec<&str> { fn fmt_files(files: &[&str]) -> i32 { let (config, _) = - load_config::(Some(Path::new(".")), None).expect("couldn't load config"); + load_config::(None, Some(Path::new(".")), None).expect("couldn't load config"); let mut exit_code = 0; let mut out = stdout(); diff --git a/src/ignore_path.rs b/src/ignore_path.rs index d955949496a..e00878bbb7b 100644 --- a/src/ignore_path.rs +++ b/src/ignore_path.rs @@ -42,7 +42,7 @@ mod test { use std::path::{Path, PathBuf}; let config = - Config::from_toml(r#"ignore = ["foo.rs", "bar_dir/*"]"#, Path::new("")).unwrap(); + Config::from_toml(None, r#"ignore = ["foo.rs", "bar_dir/*"]"#, Path::new("")).unwrap(); let ignore_path_set = IgnorePathSet::from_ignore_list(&config.ignore()).unwrap(); assert!(ignore_path_set.is_match(&FileName::Real(PathBuf::from("src/foo.rs")))); diff --git a/src/lib.rs b/src/lib.rs index 0f111f32a4a..cd2ee554283 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,7 +49,7 @@ use crate::utils::indent_next_line; pub use crate::config::{ load_config, CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName, NewlineStyle, - Range, Verbosity, + Range, StyleEdition, Verbosity, }; pub use crate::format_report_formatter::{FormatReportFormatter, FormatReportFormatterBuilder}; diff --git a/src/parse/session.rs b/src/parse/session.rs index 1956e1b8cd7..fd642646ec0 100644 --- a/src/parse/session.rs +++ b/src/parse/session.rs @@ -414,7 +414,9 @@ mod tests { } fn get_ignore_list(config: &str) -> IgnoreList { - Config::from_toml(config, Path::new("")).unwrap().ignore() + Config::from_toml(None, config, Path::new("")) + .unwrap() + .ignore() } #[test] diff --git a/src/test/mod.rs b/src/test/mod.rs index cfad4a8ed0e..49ff5bb1fc4 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -737,7 +737,7 @@ fn idempotent_check( ) -> Result { let sig_comments = read_significant_comments(filename); let config = if let Some(ref config_file_path) = opt_config { - Config::from_toml_path(config_file_path).expect("`rustfmt.toml` not found") + Config::from_toml_path(None, config_file_path).expect("`rustfmt.toml` not found") } else { read_config(filename) }; @@ -780,7 +780,7 @@ fn get_config(config_file: Option<&Path>) -> Config { .read_to_string(&mut def_config) .expect("Couldn't read config"); - Config::from_toml(&def_config, Path::new("tests/config/")).expect("invalid TOML") + Config::from_toml(None, &def_config, Path::new("tests/config/")).expect("invalid TOML") } // Reads significant comments of the form: `// rustfmt-key: value` into a hash map. diff --git a/tests/rustfmt/main.rs b/tests/rustfmt/main.rs index 4936a717463..f02dba9ab53 100644 --- a/tests/rustfmt/main.rs +++ b/tests/rustfmt/main.rs @@ -78,6 +78,17 @@ fn print_config() { remove_file("minimal-config").unwrap(); } +#[rustfmt_only_ci_test] +#[test] +fn print_default_config_with_style_edition() { + for edition in ["2015", "2018", "2021", "2024"] { + assert_that!( + &["--print-config", "default", "--style-edition", edition], + contains(&format!("style_edition = {:?}", edition)) + ); + } +} + #[rustfmt_only_ci_test] #[test] fn inline_config() {