X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fconfig%2Foptions.rs;h=c0491630c000e2bdd4c5bbbdc87cd80b20a46aa2;hb=612e8d5b9be72713a081370c85cc5bed30b6fae6;hp=be3aa1f13d4257b10c4e06efe2eb82db2c7433ed;hpb=c34a387eb756f435c947557dd219986d98d00a00;p=rust.git diff --git a/src/config/options.rs b/src/config/options.rs index be3aa1f13d4..c0491630c00 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -1,223 +1,210 @@ -// 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 or the MIT license -// , 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(&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::$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) -> 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 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 { - $( - 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,164 +283,160 @@ fn from_str(_: &str) -> Result { } } -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); +#[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 ::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 { - 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, - quiet: bool, - verbose: bool, - pub(super) config_path: Option, - write_mode: Option, - check: bool, - color: Option, - file_lines: FileLines, // Default is all lines in all files. - unstable_features: bool, - error_on_unformatted: Option, -} - -impl CliOptions { - pub fn from_matches(matches: &Matches) -> FmtResult { - 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 write_mode) = matches.opt_str("write-mode") { - if options.check { - return Err(format_err!("Invalid to set write-mode and `--check`")); - } - if let Ok(write_mode) = WriteMode::from_str(write_mode) { - options.write_mode = Some(write_mode); - } else { - return Err(format_err!( - "Invalid write-mode: {}, expected one of {}", - write_mode, - 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 { + 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 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 { + rustc_span::edition::Edition::partial_cmp(&(*self).into(), &(*other).into()) } } + +/// 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, +}