X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fconfig%2Foptions.rs;h=c0491630c000e2bdd4c5bbbdc87cd80b20a46aa2;hb=612e8d5b9be72713a081370c85cc5bed30b6fae6;hp=be32c14291656430e45bbb4beefe3be015b155a4;hpb=7a3b7c9275f057aa1d71ea7516ef33a44bdffb7f;p=rust.git diff --git a/src/config/options.rs b/src/config/options.rs index be32c142916..c0491630c00 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -1,290 +1,178 @@ -use std::collections::HashSet; +use std::collections::{hash_set, HashSet}; +use std::fmt; use std::path::{Path, PathBuf}; +use std::str::FromStr; -use atty; +use itertools::Itertools; +use rustfmt_config_proc_macro::config_type; +use serde::de::{SeqAccess, Visitor}; +use serde::ser::SerializeSeq; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use crate::config::config_type::ConfigType; use crate::config::lists::*; -use crate::config::{Config, FileName}; - -/// Macro that will stringify the enum variants or a provided textual repr -#[macro_export] -macro_rules! configuration_option_enum_stringify { - ($variant:ident) => { - stringify!($variant) - }; - - ($_variant:ident: $value:expr) => { - stringify!($value) - }; -} - -/// Macro for deriving implementations of Serialize/Deserialize for enums -#[macro_export] -macro_rules! impl_enum_serialize_and_deserialize { - ( $e:ident, $( $variant:ident $(: $value:expr)* ),* ) => { - impl ::serde::ser::Serialize for $e { - fn serialize(&self, serializer: S) -> Result - where S: ::serde::ser::Serializer - { - use serde::ser::Error; - - // We don't know whether the user of the macro has given us all options. - #[allow(unreachable_patterns)] - match *self { - $( - $e::$variant => serializer.serialize_str( - configuration_option_enum_stringify!($variant $(: $value)*) - ), - )* - _ => { - Err(S::Error::custom(format!("Cannot serialize {:?}", self))) - } - } - } - } - - impl<'de> ::serde::de::Deserialize<'de> for $e { - fn deserialize(d: D) -> Result - where D: ::serde::Deserializer<'de> { - use serde::de::{Error, Visitor}; - use std::marker::PhantomData; - use std::fmt; - struct StringOnly(PhantomData); - impl<'de, T> Visitor<'de> for StringOnly - where T: ::serde::Deserializer<'de> { - type Value = String; - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("string") - } - fn visit_str(self, value: &str) -> Result { - Ok(String::from(value)) - } - } - let s = d.deserialize_string(StringOnly::(PhantomData))?; - $( - if configuration_option_enum_stringify!($variant $(: $value)*) - .eq_ignore_ascii_case(&s) { - return Ok($e::$variant); - } - )* - static ALLOWED: &'static[&str] = &[ - $(configuration_option_enum_stringify!($variant $(: $value)*),)*]; - Err(D::Error::unknown_variant(&s, ALLOWED)) - } - } - - impl ::std::str::FromStr for $e { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - $( - if configuration_option_enum_stringify!($variant $(: $value)*) - .eq_ignore_ascii_case(s) { - return Ok($e::$variant); - } - )* - Err("Bad variant") - } - } +use crate::config::Config; - impl ConfigType for $e { - fn doc_hint() -> String { - let mut variants = Vec::new(); - $( - variants.push( - configuration_option_enum_stringify!($variant $(: $value)*) - ); - )* - format!("[{}]", variants.join("|")) - } - } - }; -} - -macro_rules! configuration_option_enum { - ($e:ident: $( $name:ident $(: $value:expr)* ),+ $(,)*) => ( - #[derive(Copy, Clone, Eq, PartialEq)] - pub enum $e { - $( $name ),+ - } - - impl ::std::fmt::Debug for $e { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - f.write_str(match self { - $( - $e::$name => configuration_option_enum_stringify!($name $(: $value)*), - )+ - }) - } - } - - impl_enum_serialize_and_deserialize!($e, $( $name $(: $value)* ),+); - ); -} - -configuration_option_enum! { NewlineStyle: - Auto, // Auto-detect based on the raw source input - Windows, // \r\n - Unix, // \n - Native, // \r\n in Windows, \n on other platforms -} - -impl NewlineStyle { - fn auto_detect(raw_input_text: &str) -> NewlineStyle { - if let Some(pos) = raw_input_text.find('\n') { - let pos = pos.saturating_sub(1); - if let Some('\r') = raw_input_text.chars().nth(pos) { - NewlineStyle::Windows - } else { - NewlineStyle::Unix - } - } else { - NewlineStyle::Native - } - } - - fn native() -> NewlineStyle { - if cfg!(windows) { - NewlineStyle::Windows - } else { - NewlineStyle::Unix - } - } - - /// Apply this newline style to the formatted text. When the style is set - /// to `Auto`, the `raw_input_text` is used to detect the existing line - /// endings. - /// - /// If the style is set to `Auto` and `raw_input_text` contains no - /// newlines, the `Native` style will be used. - pub(crate) fn apply(self, formatted_text: &mut String, raw_input_text: &str) { - use crate::NewlineStyle::*; - let mut style = self; - if style == Auto { - style = Self::auto_detect(raw_input_text); - } - if style == Native { - style = Self::native(); - } - match style { - Windows => { - let mut transformed = String::with_capacity(2 * formatted_text.capacity()); - for c in formatted_text.chars() { - match c { - '\n' => transformed.push_str("\r\n"), - '\r' => continue, - c => transformed.push(c), - } - } - *formatted_text = transformed; - } - Unix => return, - Native => unreachable!("NewlineStyle::Native"), - Auto => unreachable!("NewlineStyle::Auto"), - } - } +#[config_type] +pub enum NewlineStyle { + /// Auto-detect based on the raw source input. + Auto, + /// Force CRLF (`\r\n`). + Windows, + /// Force CR (`\n). + Unix, + /// `\r\n` in Windows, `\n`` on other platforms. + Native, } -configuration_option_enum! { BraceStyle: +#[config_type] +/// Where to put the opening brace of items (`fn`, `impl`, etc.). +pub enum BraceStyle { + /// Put the opening brace on the next line. AlwaysNextLine, + /// Put the opening brace on the same line, if possible. PreferSameLine, - // Prefer same line except where there is a where-clause, in which case force - // the brace to the next line. + /// Prefer the same line except where there is a where-clause, in which + /// case force the brace to be put on the next line. SameLineWhere, } -configuration_option_enum! { ControlBraceStyle: - // K&R style, Rust community default +#[config_type] +/// Where to put the opening brace of conditional expressions (`if`, `match`, etc.). +pub enum ControlBraceStyle { + /// K&R style, Rust community default AlwaysSameLine, - // Stroustrup style + /// Stroustrup style ClosingNextLine, - // Allman style + /// Allman style AlwaysNextLine, } -configuration_option_enum! { IndentStyle: - // First line on the same line as the opening brace, all lines aligned with - // the first line. +#[config_type] +/// How to indent. +pub enum IndentStyle { + /// First line on the same line as the opening brace, all lines aligned with + /// the first line. Visual, - // First line is on a new line and all lines align with block indent. + /// First line is on a new line and all lines align with **block** indent. Block, } -configuration_option_enum! { Density: - // Fit as much on one line as possible. +#[config_type] +/// How to place a list-like items. +/// FIXME: Issue-3581: this should be renamed to ItemsLayout when publishing 2.0 +pub enum Density { + /// Fit as much on one line as possible. Compressed, - // Use more lines. + /// Items are placed horizontally if sufficient space, vertically otherwise. Tall, - // Place every item on a separate line. + /// Place every item on a separate line. Vertical, } -configuration_option_enum! { TypeDensity: - // No spaces around "=" and "+" +#[config_type] +/// Spacing around type combinators. +pub enum TypeDensity { + /// No spaces around "=" and "+" Compressed, - // Spaces around " = " and " + " + /// Spaces around " = " and " + " Wide, } -configuration_option_enum! { Heuristics: - // Turn off any heuristics +#[config_type] +/// To what extent does rustfmt pursue its heuristics? +pub enum Heuristics { + /// Turn off any heuristics Off, - // Turn on max heuristics + /// Turn on max heuristics Max, - // Use Rustfmt's defaults + /// Use Rustfmt's defaults Default, } impl Density { - pub fn to_list_tactic(self) -> ListTactic { + pub fn to_list_tactic(self, len: usize) -> ListTactic { match self { Density::Compressed => ListTactic::Mixed, Density::Tall => ListTactic::HorizontalVertical, + Density::Vertical if len == 1 => ListTactic::Horizontal, Density::Vertical => ListTactic::Vertical, } } } -configuration_option_enum! { ReportTactic: +#[config_type] +/// Configuration for import groups, i.e. sets of imports separated by newlines. +pub enum GroupImportsTactic { + /// Keep groups as they are. + Preserve, + /// Discard existing groups, and create new groups for + /// 1. `std` / `core` / `alloc` imports + /// 2. other imports + /// 3. `self` / `crate` / `super` imports + StdExternalCrate, +} + +#[config_type] +/// How to merge imports. +pub enum ImportGranularity { + /// Do not merge imports. + Preserve, + /// Use one `use` statement per crate. + Crate, + /// Use one `use` statement per module. + Module, + /// Use one `use` statement per imported item. + Item, +} + +#[config_type] +pub enum ReportTactic { Always, Unnumbered, Never, } -// What Rustfmt should emit. Mostly corresponds to the `--emit` command line -// option. -configuration_option_enum! { EmitMode: - // Emits to files. +/// What Rustfmt should emit. Mostly corresponds to the `--emit` command line +/// option. +#[config_type] +pub enum EmitMode { + /// Emits to files. Files, - // Writes the output to stdout. + /// Writes the output to stdout. Stdout, - // Displays how much of the input file was processed + /// Displays how much of the input file was processed Coverage, - // Unfancy stdout + /// Unfancy stdout Checkstyle, - // Output the changed lines (for internal value only) + /// Writes the resulting diffs in a JSON format. Returns an empty array + /// `[]` if there were no diffs. + Json, + /// Output the changed lines (for internal value only) ModifiedLines, - // Checks if a diff can be generated. If so, rustfmt outputs a diff and quits with exit code 1. - // This option is designed to be run in CI where a non-zero exit signifies non-standard code - // formatting. Used for `--check`. + /// Checks if a diff can be generated. If so, rustfmt outputs a diff and + /// quits with exit code 1. + /// This option is designed to be run in CI where a non-zero exit signifies + /// non-standard code formatting. Used for `--check`. Diff, } -// Client-preference for coloured output. -configuration_option_enum! { Color: - // Always use color, whether it is a piped or terminal output +/// Client-preference for coloured output. +#[config_type] +pub enum Color { + /// Always use color, whether it is a piped or terminal output Always, - // Never use color + /// Never use color Never, - // Automatically use color, if supported by terminal + /// Automatically use color, if supported by terminal Auto, } -configuration_option_enum! { Version: - // 1.x.y +#[config_type] +/// rustfmt format style version. +pub enum Version { + /// 1.x.y. When specified, rustfmt will format in the same style as 1.0.0. One, - // 2.x.y + /// 2.x.y. When specified, rustfmt will format in the the latest style. Two, } @@ -292,19 +180,20 @@ impl Color { /// Whether we should use a coloured terminal. pub fn use_colored_tty(self) -> bool { match self { - Color::Always => true, + Color::Always | Color::Auto => true, Color::Never => false, - Color::Auto => atty::is(atty::Stream::Stdout), } } } -// How chatty should Rustfmt be? -configuration_option_enum! { Verbosity: - // Emit more. +/// How chatty should Rustfmt be? +#[config_type] +pub enum Verbosity { + /// Emit more. Verbose, + /// Default. Normal, - // Emit as little as possible. + /// Emit as little as possible. Quiet, } @@ -332,6 +221,12 @@ pub struct WidthHeuristics { pub single_line_if_else_max_width: usize, } +impl fmt::Display for WidthHeuristics { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + impl WidthHeuristics { // Using this WidthHeuristics means we ignore heuristics. pub fn null() -> WidthHeuristics { @@ -395,40 +290,93 @@ fn default() -> EmitMode { } /// A set of directories, files and modules that rustfmt should ignore. -#[derive(Default, Deserialize, Serialize, Clone, Debug, PartialEq)] -pub struct IgnoreList(HashSet); +#[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 IgnoreList { - pub fn add_prefix(&mut self, dir: &Path) { - self.0 = self - .0 - .iter() - .map(|s| { - if s.has_root() { - s.clone() - } else { - let mut path = PathBuf::from(dir); - path.push(s); - path - } - }) - .collect(); +impl fmt::Display for IgnoreList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "[{}]", + self.path_set + .iter() + .format_with(", ", |path, f| f(&format_args!( + "{}", + path.to_string_lossy() + ))) + ) } +} - fn skip_file_inner(&self, file: &Path) -> bool { - self.0.iter().any(|path| file.starts_with(path)) +impl Serialize for IgnoreList { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.path_set.len()))?; + for e in &self.path_set { + seq.serialize_element(e)?; + } + seq.end() } +} - pub fn skip_file(&self, file: &FileName) -> bool { - if let FileName::Real(ref path) = file { - self.skip_file_inner(path) - } else { - false +impl<'de> Deserialize<'de> for IgnoreList { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct HashSetVisitor; + impl<'v> Visitor<'v> for HashSetVisitor { + type Value = HashSet; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a sequence of path") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'v>, + { + let mut path_set = HashSet::new(); + while let Some(elem) = seq.next_element()? { + path_set.insert(elem); + } + Ok(path_set) + } } + Ok(IgnoreList { + path_set: deserializer.deserialize_seq(HashSetVisitor)?, + rustfmt_toml_path: PathBuf::new(), + }) + } +} + +impl<'a> IntoIterator for &'a IgnoreList { + type Item = &'a PathBuf; + type IntoIter = hash_set::Iter<'a, PathBuf>; + + fn into_iter(self) -> Self::IntoIter { + self.path_set.iter() } } -impl ::std::str::FromStr for IgnoreList { +impl IgnoreList { + pub fn add_prefix(&mut self, dir: &Path) { + self.rustfmt_toml_path = dir.to_path_buf(); + } + + pub fn rustfmt_toml_path(&self) -> &Path { + &self.rustfmt_toml_path + } +} + +impl FromStr for IgnoreList { type Err = &'static str; fn from_str(_: &str) -> Result { @@ -443,10 +391,21 @@ pub trait CliOptions { fn config_path(&self) -> Option<&Path>; } -/// The edition of the compiler (RFC 2052) -configuration_option_enum! { Edition: - Edition2015: 2015, - Edition2018: 2018, +/// The edition of the syntax and semntics of code (RFC 2052). +#[config_type] +pub enum Edition { + #[value = "2015"] + #[doc_hint = "2015"] + /// Edition 2015. + Edition2015, + #[value = "2018"] + #[doc_hint = "2018"] + /// Edition 2018. + Edition2018, + #[value = "2021"] + #[doc_hint = "2021"] + /// Edition 2021. + Edition2021, } impl Default for Edition { @@ -455,67 +414,29 @@ fn default() -> Edition { } } -impl Edition { - pub(crate) fn to_libsyntax_pos_edition(self) -> syntax_pos::edition::Edition { - match self { - Edition::Edition2015 => syntax_pos::edition::Edition::Edition2015, - Edition::Edition2018 => syntax_pos::edition::Edition::Edition2018, +impl From for rustc_span::edition::Edition { + fn from(edition: Edition) -> Self { + match edition { + Edition::Edition2015 => Self::Edition2015, + Edition::Edition2018 => Self::Edition2018, + Edition::Edition2021 => Self::Edition2021, } } } -#[test] -fn test_newline_style_auto_detect() { - let lf = "One\nTwo\nThree"; - let crlf = "One\r\nTwo\r\nThree"; - let none = "One Two Three"; - - assert_eq!(NewlineStyle::Unix, NewlineStyle::auto_detect(lf)); - assert_eq!(NewlineStyle::Windows, NewlineStyle::auto_detect(crlf)); - assert_eq!(NewlineStyle::Native, NewlineStyle::auto_detect(none)); -} - -#[test] -fn test_newline_style_auto_apply() { - let auto = NewlineStyle::Auto; - - let formatted_text = "One\nTwo\nThree"; - let raw_input_text = "One\nTwo\nThree"; - - let mut out = String::from(formatted_text); - auto.apply(&mut out, raw_input_text); - assert_eq!("One\nTwo\nThree", &out, "auto should detect 'lf'"); - - let formatted_text = "One\nTwo\nThree"; - let raw_input_text = "One\r\nTwo\r\nThree"; - - let mut out = String::from(formatted_text); - auto.apply(&mut out, raw_input_text); - assert_eq!("One\r\nTwo\r\nThree", &out, "auto should detect 'crlf'"); - - #[cfg(not(windows))] - { - let formatted_text = "One\nTwo\nThree"; - let raw_input_text = "One Two Three"; - - let mut out = String::from(formatted_text); - auto.apply(&mut out, raw_input_text); - assert_eq!( - "One\nTwo\nThree", &out, - "auto-native-unix should detect 'lf'" - ); +impl PartialOrd for Edition { + fn partial_cmp(&self, other: &Edition) -> Option { + rustc_span::edition::Edition::partial_cmp(&(*self).into(), &(*other).into()) } +} - #[cfg(windows)] - { - let formatted_text = "One\nTwo\nThree"; - let raw_input_text = "One Two Three"; - - let mut out = String::from(formatted_text); - auto.apply(&mut out, raw_input_text); - assert_eq!( - "One\r\nTwo\r\nThree", &out, - "auto-native-windows should detect 'crlf'" - ); - } +/// Controls how rustfmt should handle leading pipes on match arms. +#[config_type] +pub enum MatchArmLeadingPipe { + /// Place leading pipes on all match arms + Always, + /// Never emit leading pipes on match arms + Never, + /// Preserve any existing leading pipes + Preserve, }