// 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 config::config_type::ConfigType; use config::lists::*; use config::{Config, FileName}; use isatty::stdout_isatty; use std::collections::HashSet; use std::path::{Path, PathBuf}; /// 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))) } } } } 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)) } } 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 } configuration_option_enum! { BraceStyle: AlwaysNextLine, PreferSameLine, // Prefer same line except where there is a where clause, in which case force // the brace to the next line. SameLineWhere, } configuration_option_enum! { ControlBraceStyle: // K&R style, Rust community default AlwaysSameLine, // Stroustrup style ClosingNextLine, // Allman style AlwaysNextLine, } configuration_option_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. Block, } configuration_option_enum! { Density: // Fit as much on one line as possible. Compressed, // Use more lines. Tall, // Place every item on a separate line. Vertical, } configuration_option_enum! { TypeDensity: // No spaces around "=" and "+" Compressed, // Spaces around " = " and " + " Wide, } configuration_option_enum! { Heuristics: // Turn off any heuristics Off, // Use Rustfmt's defaults Default, } impl Density { pub fn to_list_tactic(self) -> ListTactic { match self { Density::Compressed => ListTactic::Mixed, Density::Tall => ListTactic::HorizontalVertical, Density::Vertical => ListTactic::Vertical, } } } configuration_option_enum! { ReportTactic: Always, Unnumbered, Never, } // What Rustfmt should emit. Mostly corresponds to the `--emit` command line // option. configuration_option_enum! { EmitMode: // Emits to files. Files, // Writes the output to stdout. Stdout, // Displays how much of the input file was processed Coverage, // Unfancy stdout Checkstyle, // 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, } // Client-preference for coloured output. configuration_option_enum! { Color: // Always use color, whether it is a piped or terminal output Always, // Never use color Never, // Automatically use color, if supported by terminal Auto, } impl Color { /// Whether we should use a coloured terminal. pub fn use_colored_tty(&self) -> bool { match self { Color::Always => true, Color::Never => false, Color::Auto => stdout_isatty(), } } } // How chatty should Rustfmt be? configuration_option_enum! { Verbosity: // Emit more. Verbose, Normal, // Emit as little as possible. Quiet, } #[derive(Deserialize, Serialize, Clone, Debug)] 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 in the body of a struct lit before falling back to // vertical formatting. pub struct_lit_width: usize, // Maximum width in the body of a struct variant before falling back // to vertical formatting. pub struct_variant_width: usize, // Maximum width of an array literal before falling back to vertical // formatting. pub array_width: usize, // Maximum length of a chain to fit on a single line. pub chain_width: usize, // Maximum line length for single line if-else expressions. A value // of zero means always break if-else expressions. pub single_line_if_else_max_width: usize, } impl WidthHeuristics { // Using this WidthHeuristics means we ignore heuristics. pub fn null() -> WidthHeuristics { WidthHeuristics { fn_call_width: usize::max_value(), struct_lit_width: 0, struct_variant_width: 0, array_width: usize::max_value(), chain_width: usize::max_value(), single_line_if_else_max_width: 0, } } // scale the default WidthHeuristics according to max_width pub fn scaled(max_width: usize) -> WidthHeuristics { const DEFAULT_MAX_WIDTH: usize = 100; let max_width_ratio = if max_width > DEFAULT_MAX_WIDTH { let ratio = max_width as f32 / DEFAULT_MAX_WIDTH as f32; // round to the closest 0.1 (ratio * 10.0).round() / 10.0 } else { 1.0 }; WidthHeuristics { fn_call_width: (60.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, chain_width: (60.0 * max_width_ratio).round() as usize, single_line_if_else_max_width: (50.0 * max_width_ratio).round() as usize, } } } impl ::std::str::FromStr for WidthHeuristics { type Err = &'static str; fn from_str(_: &str) -> Result { Err("WidthHeuristics is not parsable") } } 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); 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(); } fn skip_file_inner(&self, file: &Path) -> bool { self.0.iter().any(|path| file.starts_with(path)) } pub fn skip_file(&self, file: &FileName) -> bool { if let FileName::Real(ref path) = file { self.skip_file_inner(path) } else { false } } } impl ::std::str::FromStr for IgnoreList { type Err = &'static str; fn from_str(_: &str) -> Result { Err("IgnoreList is not parsable") } } /// 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>; }