1 // Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 use syntax::codemap::FileName;
13 use config::config_type::ConfigType;
14 use config::file_lines::FileLines;
18 use failure::{self, err_msg};
21 use std::collections::HashSet;
22 use std::path::{Path, PathBuf};
23 use std::str::FromStr;
25 /// Macro for deriving implementations of Serialize/Deserialize for enums
27 macro_rules! impl_enum_serialize_and_deserialize {
28 ( $e:ident, $( $x:ident ),* ) => {
29 impl ::serde::ser::Serialize for $e {
30 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
31 where S: ::serde::ser::Serializer
33 use serde::ser::Error;
35 // We don't know whether the user of the macro has given us all options.
36 #[allow(unreachable_patterns)]
39 $e::$x => serializer.serialize_str(stringify!($x)),
42 Err(S::Error::custom(format!("Cannot serialize {:?}", self)))
48 impl<'de> ::serde::de::Deserialize<'de> for $e {
49 fn deserialize<D>(d: D) -> Result<Self, D::Error>
50 where D: ::serde::Deserializer<'de> {
51 use serde::de::{Error, Visitor};
52 use std::marker::PhantomData;
54 struct StringOnly<T>(PhantomData<T>);
55 impl<'de, T> Visitor<'de> for StringOnly<T>
56 where T: ::serde::Deserializer<'de> {
58 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
59 formatter.write_str("string")
61 fn visit_str<E>(self, value: &str) -> Result<String, E> {
62 Ok(String::from(value))
65 let s = d.deserialize_string(StringOnly::<D>(PhantomData))?;
67 if stringify!($x).eq_ignore_ascii_case(&s) {
71 static ALLOWED: &'static[&str] = &[$(stringify!($x),)*];
72 Err(D::Error::unknown_variant(&s, ALLOWED))
76 impl ::std::str::FromStr for $e {
77 type Err = &'static str;
79 fn from_str(s: &str) -> Result<Self, Self::Err> {
81 if stringify!($x).eq_ignore_ascii_case(s) {
89 impl ConfigType for $e {
90 fn doc_hint() -> String {
91 let mut variants = Vec::new();
93 variants.push(stringify!($x));
95 format!("[{}]", variants.join("|"))
101 macro_rules! configuration_option_enum{
102 ($e:ident: $( $x:ident ),+ $(,)*) => {
103 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
108 impl_enum_serialize_and_deserialize!($e, $( $x ),+);
112 configuration_option_enum! { NewlineStyle:
115 Native, // \r\n in Windows, \n on other platforms
118 configuration_option_enum! { BraceStyle:
121 // Prefer same line except where there is a where clause, in which case force
122 // the brace to the next line.
126 configuration_option_enum! { ControlBraceStyle:
127 // K&R style, Rust community default
135 configuration_option_enum! { IndentStyle:
136 // First line on the same line as the opening brace, all lines aligned with
139 // First line is on a new line and all lines align with block indent.
143 configuration_option_enum! { Density:
144 // Fit as much on one line as possible.
148 // Place every item on a separate line.
152 configuration_option_enum! { TypeDensity:
153 // No spaces around "=" and "+"
155 // Spaces around " = " and " + "
160 pub fn to_list_tactic(self) -> ListTactic {
162 Density::Compressed => ListTactic::Mixed,
163 Density::Tall => ListTactic::HorizontalVertical,
164 Density::Vertical => ListTactic::Vertical,
169 configuration_option_enum! { ReportTactic:
175 configuration_option_enum! { WriteMode:
176 // Overwrites original file without backup.
178 // Backs the original file up and overwrites the original.
180 // Writes the output to stdout.
182 // Displays how much of the input file was processed
186 // Output the changed lines (for internal value only)
188 // Checks if a diff can be generated. If so, rustfmt outputs a diff and quits with exit code 1.
189 // This option is designed to be run in CI where a non-zero exit signifies non-standard code
192 // Rustfmt shouldn't output anything formatting-like (e.g., emit a help message).
196 const STABLE_WRITE_MODES: [WriteMode; 4] = [
198 WriteMode::Overwrite,
203 configuration_option_enum! { Color:
204 // Always use color, whether it is a piped or terminal output
208 // Automatically use color, if supported by terminal
212 configuration_option_enum! { Verbosity:
216 // Emit as little as possible.
220 #[derive(Deserialize, Serialize, Clone, Debug)]
221 pub struct WidthHeuristics {
222 // Maximum width of the args of a function call before falling back
223 // to vertical formatting.
224 pub fn_call_width: usize,
225 // Maximum width in the body of a struct lit before falling back to
226 // vertical formatting.
227 pub struct_lit_width: usize,
228 // Maximum width in the body of a struct variant before falling back
229 // to vertical formatting.
230 pub struct_variant_width: usize,
231 // Maximum width of an array literal before falling back to vertical
233 pub array_width: usize,
234 // Maximum length of a chain to fit on a single line.
235 pub chain_width: usize,
236 // Maximum line length for single line if-else expressions. A value
237 // of zero means always break if-else expressions.
238 pub single_line_if_else_max_width: usize,
241 impl WidthHeuristics {
242 // Using this WidthHeuristics means we ignore heuristics.
243 pub fn null() -> WidthHeuristics {
245 fn_call_width: usize::max_value(),
247 struct_variant_width: 0,
248 array_width: usize::max_value(),
249 chain_width: usize::max_value(),
250 single_line_if_else_max_width: 0,
254 // scale the default WidthHeuristics according to max_width
255 pub fn scaled(max_width: usize) -> WidthHeuristics {
256 const DEFAULT_MAX_WIDTH: usize = 100;
257 let max_width_ratio = if max_width > DEFAULT_MAX_WIDTH {
258 let ratio = max_width as f32 / DEFAULT_MAX_WIDTH as f32;
259 // round to the closest 0.1
260 (ratio * 10.0).round() / 10.0
265 fn_call_width: (60.0 * max_width_ratio).round() as usize,
266 struct_lit_width: (18.0 * max_width_ratio).round() as usize,
267 struct_variant_width: (35.0 * max_width_ratio).round() as usize,
268 array_width: (60.0 * max_width_ratio).round() as usize,
269 chain_width: (60.0 * max_width_ratio).round() as usize,
270 single_line_if_else_max_width: (50.0 * max_width_ratio).round() as usize,
275 impl ::std::str::FromStr for WidthHeuristics {
276 type Err = &'static str;
278 fn from_str(_: &str) -> Result<Self, Self::Err> {
279 Err("WidthHeuristics is not parsable")
283 impl Default for WriteMode {
284 fn default() -> WriteMode {
289 /// A set of directories, files and modules that rustfmt should ignore.
290 #[derive(Default, Deserialize, Serialize, Clone, Debug)]
291 pub struct IgnoreList(HashSet<PathBuf>);
294 pub fn add_prefix(&mut self, dir: &Path) {
302 let mut path = PathBuf::from(dir);
310 fn skip_file_inner(&self, file: &Path) -> bool {
311 self.0.iter().any(|path| file.starts_with(path))
314 pub fn skip_file(&self, file: &FileName) -> bool {
315 if let FileName::Real(ref path) = file {
316 self.skip_file_inner(path)
323 impl ::std::str::FromStr for IgnoreList {
324 type Err = &'static str;
326 fn from_str(_: &str) -> Result<Self, Self::Err> {
327 Err("IgnoreList is not parsable")
331 /// Parsed command line options.
332 #[derive(Clone, Debug, Default)]
333 pub struct CliOptions {
334 pub skip_children: Option<bool>,
337 pub config_path: Option<PathBuf>,
338 pub write_mode: WriteMode,
340 pub color: Option<Color>,
341 pub file_lines: FileLines, // Default is all lines in all files.
342 pub unstable_features: bool,
343 pub error_on_unformatted: Option<bool>,
347 pub fn from_matches(matches: &Matches) -> Result<CliOptions, failure::Error> {
348 let mut options = CliOptions::default();
349 options.verbose = matches.opt_present("verbose");
350 options.quiet = matches.opt_present("quiet");
351 if options.verbose && options.quiet {
352 return Err(format_err!("Can't use both `--verbose` and `--quiet`"));
355 let rust_nightly = option_env!("CFG_RELEASE_CHANNEL")
356 .map(|c| c == "nightly")
359 options.unstable_features = matches.opt_present("unstable-features");
362 if options.unstable_features {
363 if matches.opt_present("skip-children") {
364 options.skip_children = Some(true);
366 if matches.opt_present("error-on-unformatted") {
367 options.error_on_unformatted = Some(true);
369 if let Some(ref file_lines) = matches.opt_str("file-lines") {
370 options.file_lines = file_lines.parse().map_err(err_msg)?;
374 options.config_path = matches.opt_str("config-path").map(PathBuf::from);
376 options.check = matches.opt_present("check");
377 if let Some(ref emit_str) = matches.opt_str("emit") {
379 return Err(format_err!("Invalid to use `--emit` and `--check`"));
381 if let Ok(write_mode) = write_mode_from_emit_str(emit_str) {
382 options.write_mode = write_mode;
384 return Err(format_err!("Invalid value for `--emit`"));
388 if options.write_mode == WriteMode::Overwrite && matches.opt_present("backup") {
389 options.write_mode = WriteMode::Replace;
393 if !STABLE_WRITE_MODES.contains(&options.write_mode) {
394 return Err(format_err!(
395 "Invalid value for `--emit` - using an unstable \
396 value without `--unstable-features`",
401 if let Some(ref color) = matches.opt_str("color") {
402 match Color::from_str(color) {
403 Ok(color) => options.color = Some(color),
404 _ => return Err(format_err!("Invalid color: {}", color)),
411 pub fn apply_to(self, config: &mut Config) {
413 config.set().verbose(Verbosity::Verbose);
414 } else if self.quiet {
415 config.set().verbose(Verbosity::Quiet);
417 config.set().verbose(Verbosity::Normal);
419 config.set().file_lines(self.file_lines);
420 config.set().unstable_features(self.unstable_features);
421 if let Some(skip_children) = self.skip_children {
422 config.set().skip_children(skip_children);
424 if let Some(error_on_unformatted) = self.error_on_unformatted {
425 config.set().error_on_unformatted(error_on_unformatted);
428 config.set().write_mode(WriteMode::Check);
430 config.set().write_mode(self.write_mode);
432 if let Some(color) = self.color {
433 config.set().color(color);
437 pub fn verify_file_lines(&self, files: &[PathBuf]) {
438 for f in self.file_lines.files() {
440 FileName::Real(ref f) if files.contains(f) => {}
441 FileName::Real(_) => {
442 eprintln!("Warning: Extra file listed in file_lines option '{}'", f)
444 _ => eprintln!("Warning: Not a file '{}'", f),
450 fn write_mode_from_emit_str(emit_str: &str) -> Result<WriteMode, failure::Error> {
452 "files" => Ok(WriteMode::Overwrite),
453 "stdout" => Ok(WriteMode::Display),
454 "coverage" => Ok(WriteMode::Coverage),
455 "checkstyle" => Ok(WriteMode::Checkstyle),
456 _ => Err(format_err!("Invalid value for `--emit`")),