]> git.lizzy.rs Git - rust.git/blobdiff - src/config/options.rs
refactor: apply rustc mod parsing changes
[rust.git] / src / config / options.rs
index f167c34aedf2b2e7afa79a51e4a9465264b30c93..c0491630c000e2bdd4c5bbbdc87cd80b20a46aa2 100644 (file)
-// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
-// file at the top-level directory of this distribution and at
-// http://rust-lang.org/COPYRIGHT.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-                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: D) -> Result<Self, D::Error>
-                    where D: ::serde::Deserializer<'de> {
-                use serde::de::{Error, Visitor};
-                use std::marker::PhantomData;
-                use std::fmt;
-                struct StringOnly<T>(PhantomData<T>);
-                impl<'de, T> Visitor<'de> for StringOnly<T>
-                        where T: ::serde::Deserializer<'de> {
-                    type Value = String;
-                    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
-                        formatter.write_str("string")
-                    }
-                    fn visit_str<E>(self, value: &str) -> Result<String, E> {
-                        Ok(String::from(value))
-                    }
-                }
-                let s = d.deserialize_string(StringOnly::<D>(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<Self, Self::Err> {
-                $(
-                    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,
 }
 
@@ -302,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,
 }
 
@@ -342,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 {
@@ -405,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<PathBuf>);
+#[derive(Default, Clone, Debug, PartialEq)]
+pub struct IgnoreList {
+    /// A set of path specified in rustfmt.toml.
+    path_set: HashSet<PathBuf>,
+    /// 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    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<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct HashSetVisitor;
+        impl<'v> Visitor<'v> for HashSetVisitor {
+            type Value = HashSet<PathBuf>;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+                formatter.write_str("a sequence of path")
+            }
+
+            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
+            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<Self, Self::Err> {
@@ -453,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 {
@@ -465,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<Edition> 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<std::cmp::Ordering> {
+        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,
 }