]> git.lizzy.rs Git - rust.git/blobdiff - src/config/options.rs
refactor: apply rustc mod parsing changes
[rust.git] / src / config / options.rs
index e4ba87a88f801de0bf127ad6b72ae507223af749..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 syntax::codemap::FileName;
-
-use config::config_type::ConfigType;
-use config::file_lines::FileLines;
-use config::lists::*;
-use config::Config;
-use {FmtResult, WRITE_MODE_LIST};
-
-use failure::err_msg;
-
-use getopts::Matches;
-use std::collections::HashSet;
+use std::collections::{hash_set, HashSet};
+use std::fmt;
 use std::path::{Path, PathBuf};
 use std::str::FromStr;
 
-/// Macro for deriving implementations of Serialize/Deserialize for enums
-#[macro_export]
-macro_rules! impl_enum_serialize_and_deserialize {
-    ( $e:ident, $( $x:ident ),* ) => {
-        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::$x => serializer.serialize_str(stringify!($x)),
-                    )*
-                    _ => {
-                        Err(S::Error::custom(format!("Cannot serialize {:?}", self)))
-                    }
-                }
-            }
-        }
+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};
 
-        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 stringify!($x).eq_ignore_ascii_case(&s) {
-                      return Ok($e::$x);
-                    }
-                )*
-                static ALLOWED: &'static[&str] = &[$(stringify!($x),)*];
-                Err(D::Error::unknown_variant(&s, ALLOWED))
-            }
-        }
+use crate::config::lists::*;
+use crate::config::Config;
 
-        impl ::std::str::FromStr for $e {
-            type Err = &'static str;
-
-            fn from_str(s: &str) -> Result<Self, Self::Err> {
-                $(
-                    if stringify!($x).eq_ignore_ascii_case(s) {
-                        return Ok($e::$x);
-                    }
-                )*
-                Err("Bad variant")
-            }
-        }
-
-        impl ConfigType for $e {
-            fn doc_hint() -> String {
-                let mut variants = Vec::new();
-                $(
-                    variants.push(stringify!($x));
-                )*
-                format!("[{}]", variants.join("|"))
-            }
-        }
-    };
-}
-
-macro_rules! configuration_option_enum{
-    ($e:ident: $( $x:ident ),+ $(,)*) => {
-        #[derive(Copy, Clone, Eq, PartialEq, Debug)]
-        pub enum $e {
-            $( $x ),+
-        }
-
-        impl_enum_serialize_and_deserialize!($e, $( $x ),+);
-    }
-}
-
-configuration_option_enum! { NewlineStyle:
-    Windows, // \r\n
-    Unix, // \n
-    Native, // \r\n in Windows, \n on other platforms
+#[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,
 }
 
+#[config_type]
+/// To what extent does rustfmt pursue its heuristics?
+pub enum Heuristics {
+    /// Turn off any heuristics
+    Off,
+    /// Turn on max heuristics
+    Max,
+    /// 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,
 }
 
-configuration_option_enum! { WriteMode:
-    // Backs the original file up and overwrites the original.
-    Replace,
-    // Overwrites original file without backup.
-    Overwrite,
-    // Writes the output to stdout.
-    Display,
-    // Writes the diff to stdout.
-    Diff,
-    // Displays how much of the input file was processed
+/// 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.
+    Stdout,
+    /// Displays how much of the input file was processed
     Coverage,
-    // Unfancy stdout
+    /// Unfancy stdout
     Checkstyle,
-    // Output the changed lines (for internal value only)
-    Modified,
-    // 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.
-    Check,
-    // Rustfmt shouldn't output anything formatting-like (e.g., emit a help message).
-    None,
+    /// 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`.
+    Diff,
 }
 
-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! { Verbosity:
-    // Emit more.
+#[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. When specified, rustfmt will format in the the latest style.
+    Two,
+}
+
+impl Color {
+    /// Whether we should use a coloured terminal.
+    pub fn use_colored_tty(self) -> bool {
+        match self {
+            Color::Always | Color::Auto => true,
+            Color::Never => false,
+        }
+    }
+}
+
+/// 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,
 }
 
-#[derive(Deserialize, Serialize, Clone, Debug)]
+#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
 pub struct WidthHeuristics {
     // Maximum width of the args of a function call before falling back
     // to vertical formatting.
     pub fn_call_width: usize,
+    // Maximum width of the args of a function-like attributes before falling
+    // back to vertical formatting.
+    pub attr_fn_like_width: usize,
     // Maximum width in the body of a struct lit before falling back to
     // vertical formatting.
     pub struct_lit_width: usize,
@@ -234,11 +221,18 @@ 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 {
         WidthHeuristics {
             fn_call_width: usize::max_value(),
+            attr_fn_like_width: usize::max_value(),
             struct_lit_width: 0,
             struct_variant_width: 0,
             array_width: usize::max_value(),
@@ -247,6 +241,18 @@ pub fn null() -> WidthHeuristics {
         }
     }
 
+    pub fn set(max_width: usize) -> WidthHeuristics {
+        WidthHeuristics {
+            fn_call_width: max_width,
+            attr_fn_like_width: max_width,
+            struct_lit_width: max_width,
+            struct_variant_width: max_width,
+            array_width: max_width,
+            chain_width: max_width,
+            single_line_if_else_max_width: max_width,
+        }
+    }
+
     // scale the default WidthHeuristics according to max_width
     pub fn scaled(max_width: usize) -> WidthHeuristics {
         const DEFAULT_MAX_WIDTH: usize = 100;
@@ -259,6 +265,7 @@ pub fn scaled(max_width: usize) -> WidthHeuristics {
         };
         WidthHeuristics {
             fn_call_width: (60.0 * max_width_ratio).round() as usize,
+            attr_fn_like_width: (70.0 * max_width_ratio).round() as usize,
             struct_lit_width: (18.0 * max_width_ratio).round() as usize,
             struct_variant_width: (35.0 * max_width_ratio).round() as usize,
             array_width: (60.0 * max_width_ratio).round() as usize,
@@ -276,178 +283,160 @@ fn from_str(_: &str) -> Result<Self, Self::Err> {
     }
 }
 
-impl Default for WriteMode {
-    fn default() -> WriteMode {
-        WriteMode::Overwrite
+impl Default for EmitMode {
+    fn default() -> EmitMode {
+        EmitMode::Files
     }
 }
 
 /// A set of directories, files and modules that rustfmt should ignore.
-#[derive(Default, Deserialize, Serialize, Clone, Debug)]
-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 ::std::str::FromStr for IgnoreList {
-    type Err = &'static str;
+impl<'a> IntoIterator for &'a IgnoreList {
+    type Item = &'a PathBuf;
+    type IntoIter = hash_set::Iter<'a, PathBuf>;
 
-    fn from_str(_: &str) -> Result<Self, Self::Err> {
-        Err("IgnoreList is not parsable")
+    fn into_iter(self) -> Self::IntoIter {
+        self.path_set.iter()
     }
 }
 
-/// Parsed command line options.
-#[derive(Clone, Debug, Default)]
-pub struct CliOptions {
-    skip_children: Option<bool>,
-    quiet: bool,
-    verbose: bool,
-    pub(super) config_path: Option<PathBuf>,
-    write_mode: Option<WriteMode>,
-    check: bool,
-    color: Option<Color>,
-    file_lines: FileLines, // Default is all lines in all files.
-    unstable_features: bool,
-    error_on_unformatted: Option<bool>,
-}
-
-impl CliOptions {
-    pub fn from_matches(matches: &Matches) -> FmtResult<CliOptions> {
-        let mut options = CliOptions::default();
-        options.verbose = matches.opt_present("verbose");
-        options.quiet = matches.opt_present("quiet");
-        if options.verbose && options.quiet {
-            return Err(format_err!("Can't use both `--verbose` and `--quiet`"));
-        }
-
-        let unstable_features = matches.opt_present("unstable-features");
-        let rust_nightly = option_env!("CFG_RELEASE_CHANNEL")
-            .map(|c| c == "nightly")
-            .unwrap_or(false);
-        if unstable_features && !rust_nightly {
-            return Err(format_err!(
-                "Unstable features are only available on Nightly channel"
-            ));
-        } else {
-            options.unstable_features = unstable_features;
-        }
+impl IgnoreList {
+    pub fn add_prefix(&mut self, dir: &Path) {
+        self.rustfmt_toml_path = dir.to_path_buf();
+    }
 
-        options.config_path = matches.opt_str("config-path").map(PathBuf::from);
+    pub fn rustfmt_toml_path(&self) -> &Path {
+        &self.rustfmt_toml_path
+    }
+}
 
-        options.check = matches.opt_present("check");
-        if let Some(ref emit_str) = matches.opt_str("emit") {
-            if options.check {
-                return Err(format_err!("Invalid to use `--emit` and `--check`"));
-            }
-            if let Ok(write_mode) = write_mode_from_emit_str(emit_str) {
-                if write_mode == WriteMode::Overwrite && matches.opt_present("backup") {
-                    options.write_mode = Some(WriteMode::Replace);
-                } else {
-                    options.write_mode = Some(write_mode);
-                }
-            } else {
-                return Err(format_err!(
-                    "Invalid value for `--emit`: {}, expected one of {}",
-                    emit_str,
-                    WRITE_MODE_LIST
-                ));
-            }
-        }
+impl FromStr for IgnoreList {
+    type Err = &'static str;
 
-        if let Some(ref color) = matches.opt_str("color") {
-            match Color::from_str(color) {
-                Ok(color) => options.color = Some(color),
-                _ => return Err(format_err!("Invalid color: {}", color)),
-            }
-        }
+    fn from_str(_: &str) -> Result<Self, Self::Err> {
+        Err("IgnoreList is not parsable")
+    }
+}
 
-        if let Some(ref file_lines) = matches.opt_str("file-lines") {
-            options.file_lines = file_lines.parse().map_err(err_msg)?;
-        }
+/// Maps client-supplied options to Rustfmt's internals, mostly overriding
+/// values in a config with values from the command line.
+pub trait CliOptions {
+    fn apply_to(self, config: &mut Config);
+    fn config_path(&self) -> Option<&Path>;
+}
 
-        if matches.opt_present("skip-children") {
-            options.skip_children = Some(true);
-        }
-        if matches.opt_present("error-on-unformatted") {
-            options.error_on_unformatted = Some(true);
-        }
+/// 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,
+}
 
-        Ok(options)
+impl Default for Edition {
+    fn default() -> Edition {
+        Edition::Edition2015
     }
+}
 
-    pub fn apply_to(self, config: &mut Config) {
-        if self.verbose {
-            config.set().verbose(Verbosity::Verbose);
-        } else if self.quiet {
-            config.set().verbose(Verbosity::Quiet);
-        } else {
-            config.set().verbose(Verbosity::Normal);
-        }
-        config.set().file_lines(self.file_lines);
-        config.set().unstable_features(self.unstable_features);
-        if let Some(skip_children) = self.skip_children {
-            config.set().skip_children(skip_children);
-        }
-        if let Some(error_on_unformatted) = self.error_on_unformatted {
-            config.set().error_on_unformatted(error_on_unformatted);
-        }
-        if self.check {
-            config.set().write_mode(WriteMode::Check);
-        } else if let Some(write_mode) = self.write_mode {
-            config.set().write_mode(write_mode);
-        }
-        if let Some(color) = self.color {
-            config.set().color(color);
+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,
         }
     }
+}
 
-    pub fn verify_file_lines(&self, files: &[PathBuf]) {
-        for f in self.file_lines.files() {
-            match *f {
-                FileName::Real(ref f) if files.contains(f) => {}
-                FileName::Real(_) => {
-                    eprintln!("Warning: Extra file listed in file_lines option '{}'", f)
-                }
-                _ => eprintln!("Warning: Not a file '{}'", f),
-            }
-        }
+impl PartialOrd for Edition {
+    fn partial_cmp(&self, other: &Edition) -> Option<std::cmp::Ordering> {
+        rustc_span::edition::Edition::partial_cmp(&(*self).into(), &(*other).into())
     }
 }
 
-fn write_mode_from_emit_str(emit_str: &str) -> FmtResult<WriteMode> {
-    match emit_str {
-        "files" => Ok(WriteMode::Overwrite),
-        "stdout" => Ok(WriteMode::Display),
-        "coverage" => Ok(WriteMode::Coverage),
-        "checkstyle" => Ok(WriteMode::Checkstyle),
-        _ => Err(format_err!("Invalid value for `--emit`")),
-    }
+/// 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,
 }