]> git.lizzy.rs Git - rust.git/blobdiff - src/bin/main.rs
add new flag to list names of misformatted files (#3747)
[rust.git] / src / bin / main.rs
index 049749f8f52f087706bd497e5f40fb969738e536..bb12169329a1e079a1ad601ecfca9f9af674639b 100644 (file)
@@ -1,21 +1,8 @@
-// Copyright 2015 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.
-
-#![cfg(not(test))]
-#![feature(extern_prelude)]
-
-extern crate env_logger;
-#[macro_use]
-extern crate failure;
-extern crate getopts;
-extern crate rustfmt_nightly as rustfmt;
+use env_logger;
+use failure::{err_msg, format_err, Error as FailureError, Fail};
+use io::Error as IoError;
+
+use rustfmt_nightly as rustfmt;
 
 use std::env;
 use std::fs::File;
 use std::path::{Path, PathBuf};
 use std::str::FromStr;
 
-use failure::err_msg;
-
 use getopts::{Matches, Options};
 
-use rustfmt::{
-    load_config, CliOptions, Color, Config, EmitMode, ErrorKind, FileLines, FileName, Input,
-    Session, Verbosity,
+use crate::rustfmt::{
+    load_config, CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName,
+    FormatReportFormatterBuilder, Input, Session, Verbosity,
 };
 
 fn main() {
@@ -48,7 +33,7 @@ fn main() {
 
     // Exit with given exit code.
     //
-    // NOTE: This immediately terminates the process without doing any cleanup,
+    // NOTE: this immediately terminates the process without doing any cleanup,
     // so make sure to finish all necessary cleanup before this is called.
     std::process::exit(exit_code);
 }
@@ -62,16 +47,37 @@ enum Operation {
     },
     /// Print the help message.
     Help(HelpOp),
-    // Print version information
+    /// Print version information
     Version,
     /// Output default config to a file, or stdout if None
-    ConfigOutputDefault {
-        path: Option<String>,
-    },
+    ConfigOutputDefault { path: Option<String> },
+    /// Output current config (as if formatting to a file) to stdout
+    ConfigOutputCurrent { path: Option<String> },
     /// No file specified, read from stdin
-    Stdin {
-        input: String,
-    },
+    Stdin { input: String },
+}
+
+/// Rustfmt operations errors.
+#[derive(Fail, Debug)]
+pub enum OperationError {
+    /// An unknown help topic was requested.
+    #[fail(display = "Unknown help topic: `{}`.", _0)]
+    UnknownHelpTopic(String),
+    /// An unknown print-config option was requested.
+    #[fail(display = "Unknown print-config option: `{}`.", _0)]
+    UnknownPrintConfigTopic(String),
+    /// Attempt to generate a minimal config from standard input.
+    #[fail(display = "The `--print-config=minimal` option doesn't work with standard input.")]
+    MinimalPathWithStdin,
+    /// An io error during reading or writing.
+    #[fail(display = "io error: {}", _0)]
+    IoError(IoError),
+}
+
+impl From<IoError> for OperationError {
+    fn from(e: IoError) -> OperationError {
+        OperationError::IoError(e)
+    }
 }
 
 /// Arguments to `--help`
@@ -87,12 +93,12 @@ fn make_opts() -> Options {
     opts.optflag(
         "",
         "check",
-        "Run in 'check' mode. Exits with 0 if input if formatted correctly. Exits \
+        "Run in 'check' mode. Exits with 0 if input is formatted correctly. Exits \
          with 1 and prints a diff if formatting is required.",
     );
     let is_nightly = is_nightly();
     let emit_opts = if is_nightly {
-        "[files|stdout|coverage|checkstyle]"
+        "[files|stdout|coverage|checkstyle|json]"
     } else {
         "[files|stdout]"
     };
@@ -105,6 +111,7 @@ fn make_opts() -> Options {
          found reverts to the input file path",
         "[Path for the configuration file]",
     );
+    opts.optopt("", "edition", "Rust edition to use", "[2015|2018]");
     opts.optopt(
         "",
         "color",
@@ -115,8 +122,15 @@ fn make_opts() -> Options {
         "",
         "print-config",
         "Dumps a default or minimal config to PATH. A minimal config is the \
-         subset of the current config file used for formatting the current program.",
-        "[minimal|default] PATH",
+         subset of the current config file used for formatting the current program. \
+         `current` writes to stdout current config as if formatting the file at PATH.",
+        "[default|minimal|current] PATH",
+    );
+    opts.optflag(
+        "l",
+        "files-with-diff",
+        "Prints the names of mismatched files that were formatted. Prints the names of \
+         files that would be formated when used with `--check` mode. ",
     );
 
     if is_nightly {
@@ -159,32 +173,30 @@ fn make_opts() -> Options {
 }
 
 fn is_nightly() -> bool {
-    option_env!("CFG_RELEASE_CHANNEL")
-        .map(|c| c == "nightly")
-        .unwrap_or(false)
+    option_env!("CFG_RELEASE_CHANNEL").map_or(true, |c| c == "nightly" || c == "dev")
 }
 
 // Returned i32 is an exit code
-fn execute(opts: &Options) -> Result<i32, failure::Error> {
+fn execute(opts: &Options) -> Result<i32, FailureError> {
     let matches = opts.parse(env::args().skip(1))?;
     let options = GetOptsOptions::from_matches(&matches)?;
 
     match determine_operation(&matches)? {
         Operation::Help(HelpOp::None) => {
             print_usage_to_stdout(opts, "");
-            return Ok(1);
+            Ok(0)
         }
         Operation::Help(HelpOp::Config) => {
             Config::print_docs(&mut stdout(), options.unstable_features);
-            return Ok(1);
+            Ok(0)
         }
         Operation::Help(HelpOp::FileLines) => {
             print_help_file_lines();
-            return Ok(1);
+            Ok(0)
         }
         Operation::Version => {
             print_version();
-            return Ok(1);
+            Ok(0)
         }
         Operation::ConfigOutputDefault { path } => {
             let toml = Config::default().all_options().to_toml().map_err(err_msg)?;
@@ -194,17 +206,32 @@ fn execute(opts: &Options) -> Result<i32, failure::Error> {
             } else {
                 io::stdout().write_all(toml.as_bytes())?;
             }
-            return Ok(1);
+            Ok(0)
+        }
+        Operation::ConfigOutputCurrent { path } => {
+            let path = match path {
+                Some(path) => path,
+                None => return Err(format_err!("PATH required for `--print-config current`")),
+            };
+
+            let file = PathBuf::from(path);
+            let file = file.canonicalize().unwrap_or(file);
+
+            let (config, _) = load_config(Some(file.parent().unwrap()), Some(options.clone()))?;
+            let toml = config.all_options().to_toml().map_err(err_msg)?;
+            io::stdout().write_all(toml.as_bytes())?;
+
+            Ok(0)
         }
         Operation::Stdin { input } => format_string(input, options),
         Operation::Format {
             files,
             minimal_config_path,
-        } => format(files, minimal_config_path, options),
+        } => format(files, minimal_config_path, &options),
     }
 }
 
-fn format_string(input: String, options: GetOptsOptions) -> Result<i32, failure::Error> {
+fn format_string(input: String, options: GetOptsOptions) -> Result<i32, FailureError> {
     // try to read config from local directory
     let (mut config, _) = load_config(Some(Path::new(".")), Some(options.clone()))?;
 
@@ -236,8 +263,8 @@ fn format_string(input: String, options: GetOptsOptions) -> Result<i32, failure:
 fn format(
     files: Vec<PathBuf>,
     minimal_config_path: Option<String>,
-    options: GetOptsOptions,
-) -> Result<i32, failure::Error> {
+    options: &GetOptsOptions,
+) -> Result<i32, FailureError> {
     options.verify_file_lines(&files);
     let (config, config_path) = load_config(None, Some(options.clone()))?;
 
@@ -300,23 +327,16 @@ fn format(
     Ok(exit_code)
 }
 
-fn format_and_emit_report<T: Write>(session: &mut Session<T>, input: Input) {
+fn format_and_emit_report<T: Write>(session: &mut Session<'_, T>, input: Input) {
     match session.format(input) {
         Ok(report) => {
             if report.has_warnings() {
-                match term::stderr() {
-                    Some(ref t)
-                        if session.config.color().use_colored_tty()
-                            && t.supports_color()
-                            && t.supports_attr(term::Attr::Bold) =>
-                    {
-                        match report.fancy_print(term::stderr().unwrap()) {
-                            Ok(..) => (),
-                            Err(..) => panic!("Unable to write to stderr: {}", report),
-                        }
-                    }
-                    _ => eprintln!("{}", report),
-                }
+                eprintln!(
+                    "{}",
+                    FormatReportFormatterBuilder::new(&report)
+                        .enable_colors(should_print_with_colors(session))
+                        .build()
+                );
             }
         }
         Err(msg) => {
@@ -326,6 +346,19 @@ fn format_and_emit_report<T: Write>(session: &mut Session<T>, input: Input) {
     }
 }
 
+fn should_print_with_colors<T: Write>(session: &mut Session<'_, T>) -> bool {
+    match term::stderr() {
+        Some(ref t)
+            if session.config.color().use_colored_tty()
+                && t.supports_color()
+                && t.supports_attr(term::Attr::Bold) =>
+        {
+            true
+        }
+        _ => false,
+    }
+}
+
 fn print_usage_to_stdout(opts: &Options, reason: &str) {
     let sep = if reason.is_empty() {
         String::new()
@@ -373,7 +406,7 @@ fn print_version() {
     println!("rustfmt {}", version_info);
 }
 
-fn determine_operation(matches: &Matches) -> Result<Operation, ErrorKind> {
+fn determine_operation(matches: &Matches) -> Result<Operation, OperationError> {
     if matches.opt_present("h") {
         let topic = matches.opt_str("h");
         if topic == None {
@@ -383,20 +416,25 @@ fn determine_operation(matches: &Matches) -> Result<Operation, ErrorKind> {
         } else if topic == Some("file-lines".to_owned()) {
             return Ok(Operation::Help(HelpOp::FileLines));
         } else {
-            println!("Unknown help topic: `{}`\n", topic.unwrap());
-            return Ok(Operation::Help(HelpOp::None));
+            return Err(OperationError::UnknownHelpTopic(topic.unwrap()));
         }
     }
+    let mut free_matches = matches.free.iter();
 
     let mut minimal_config_path = None;
-    if let Some(ref kind) = matches.opt_str("print-config") {
-        let path = matches.free.get(0).cloned();
-        if kind == "default" {
-            return Ok(Operation::ConfigOutputDefault { path });
-        } else if kind == "minimal" {
-            minimal_config_path = path;
-            if minimal_config_path.is_none() {
-                println!("WARNING: PATH required for `--print-config minimal`");
+    if let Some(kind) = matches.opt_str("print-config") {
+        let path = free_matches.next().cloned();
+        match kind.as_str() {
+            "default" => return Ok(Operation::ConfigOutputDefault { path }),
+            "current" => return Ok(Operation::ConfigOutputCurrent { path }),
+            "minimal" => {
+                minimal_config_path = path;
+                if minimal_config_path.is_none() {
+                    eprintln!("WARNING: PATH required for `--print-config minimal`.");
+                }
+            }
+            _ => {
+                return Err(OperationError::UnknownPrintConfigTopic(kind));
             }
         }
     }
@@ -405,24 +443,26 @@ fn determine_operation(matches: &Matches) -> Result<Operation, ErrorKind> {
         return Ok(Operation::Version);
     }
 
+    let files: Vec<_> = free_matches
+        .map(|s| {
+            let p = PathBuf::from(s);
+            // we will do comparison later, so here tries to canonicalize first
+            // to get the expected behavior.
+            p.canonicalize().unwrap_or(p)
+        })
+        .collect();
+
     // if no file argument is supplied, read from stdin
-    if matches.free.is_empty() {
+    if files.is_empty() {
+        if minimal_config_path.is_some() {
+            return Err(OperationError::MinimalPathWithStdin);
+        }
         let mut buffer = String::new();
         io::stdin().read_to_string(&mut buffer)?;
 
         return Ok(Operation::Stdin { input: buffer });
     }
 
-    let files: Vec<_> = matches
-        .free
-        .iter()
-        .map(|s| {
-            let p = PathBuf::from(s);
-            // we will do comparison later, so here tries to canonicalize first
-            // to get the expected behavior.
-            p.canonicalize().unwrap_or(p)
-        }).collect();
-
     Ok(Operation::Format {
         files,
         minimal_config_path,
@@ -441,14 +481,16 @@ struct GetOptsOptions {
     emit_mode: EmitMode,
     backup: bool,
     check: bool,
+    edition: Option<Edition>,
     color: Option<Color>,
     file_lines: FileLines, // Default is all lines in all files.
     unstable_features: bool,
     error_on_unformatted: Option<bool>,
+    print_misformatted_file_names: bool,
 }
 
 impl GetOptsOptions {
-    pub fn from_matches(matches: &Matches) -> Result<GetOptsOptions, failure::Error> {
+    pub fn from_matches(matches: &Matches) -> Result<GetOptsOptions, FailureError> {
         let mut options = GetOptsOptions::default();
         options.verbose = matches.opt_present("verbose");
         options.quiet = matches.opt_present("quiet");
@@ -456,22 +498,40 @@ pub fn from_matches(matches: &Matches) -> Result<GetOptsOptions, failure::Error>
             return Err(format_err!("Can't use both `--verbose` and `--quiet`"));
         }
 
-        let rust_nightly = option_env!("CFG_RELEASE_CHANNEL")
-            .map(|c| c == "nightly")
-            .unwrap_or(false);
+        let rust_nightly = is_nightly();
+
         if rust_nightly {
             options.unstable_features = matches.opt_present("unstable-features");
-        }
 
-        if options.unstable_features {
-            if matches.opt_present("skip-children") {
-                options.skip_children = Some(true);
-            }
-            if matches.opt_present("error-on-unformatted") {
-                options.error_on_unformatted = Some(true);
-            }
-            if let Some(ref file_lines) = matches.opt_str("file-lines") {
-                options.file_lines = file_lines.parse().map_err(err_msg)?;
+            if options.unstable_features {
+                if matches.opt_present("skip-children") {
+                    options.skip_children = Some(true);
+                }
+                if matches.opt_present("error-on-unformatted") {
+                    options.error_on_unformatted = Some(true);
+                }
+                if let Some(ref file_lines) = matches.opt_str("file-lines") {
+                    options.file_lines = file_lines.parse().map_err(err_msg)?;
+                }
+            } else {
+                let mut unstable_options = vec![];
+                if matches.opt_present("skip-children") {
+                    unstable_options.push("`--skip-children`");
+                }
+                if matches.opt_present("error-on-unformatted") {
+                    unstable_options.push("`--error-on-unformatted`");
+                }
+                if matches.opt_present("file-lines") {
+                    unstable_options.push("`--file-lines`");
+                }
+                if !unstable_options.is_empty() {
+                    let s = if unstable_options.len() == 1 { "" } else { "s" };
+                    return Err(format_err!(
+                        "Unstable option{} ({}) used without `--unstable-features`",
+                        s,
+                        unstable_options.join(", "),
+                    ));
+                }
             }
         }
 
@@ -482,17 +542,22 @@ pub fn from_matches(matches: &Matches) -> Result<GetOptsOptions, failure::Error>
             if options.check {
                 return Err(format_err!("Invalid to use `--emit` and `--check`"));
             }
-            if let Ok(emit_mode) = emit_mode_from_emit_str(emit_str) {
-                options.emit_mode = emit_mode;
-            } else {
-                return Err(format_err!("Invalid value for `--emit`"));
-            }
+
+            options.emit_mode = emit_mode_from_emit_str(emit_str)?;
+        }
+
+        if let Some(ref edition_str) = matches.opt_str("edition") {
+            options.edition = Some(edition_from_edition_str(edition_str)?);
         }
 
         if matches.opt_present("backup") {
             options.backup = true;
         }
 
+        if matches.opt_present("files-with-diff") {
+            options.print_misformatted_file_names = true;
+        }
+
         if !rust_nightly {
             if !STABLE_EMIT_MODES.contains(&options.emit_mode) {
                 return Err(format_err!(
@@ -542,6 +607,9 @@ fn apply_to(self, config: &mut Config) {
         if let Some(error_on_unformatted) = self.error_on_unformatted {
             config.set().error_on_unformatted(error_on_unformatted);
         }
+        if let Some(edition) = self.edition {
+            config.set().edition(edition);
+        }
         if self.check {
             config.set().emit_mode(EmitMode::Diff);
         } else {
@@ -553,6 +621,9 @@ fn apply_to(self, config: &mut Config) {
         if let Some(color) = self.color {
             config.set().color(color);
         }
+        if self.print_misformatted_file_names {
+            config.set().print_misformatted_file_names(true);
+        }
     }
 
     fn config_path(&self) -> Option<&Path> {
@@ -560,12 +631,21 @@ fn config_path(&self) -> Option<&Path> {
     }
 }
 
-fn emit_mode_from_emit_str(emit_str: &str) -> Result<EmitMode, failure::Error> {
+fn edition_from_edition_str(edition_str: &str) -> Result<Edition, FailureError> {
+    match edition_str {
+        "2015" => Ok(Edition::Edition2015),
+        "2018" => Ok(Edition::Edition2018),
+        _ => Err(format_err!("Invalid value for `--edition`")),
+    }
+}
+
+fn emit_mode_from_emit_str(emit_str: &str) -> Result<EmitMode, FailureError> {
     match emit_str {
         "files" => Ok(EmitMode::Files),
         "stdout" => Ok(EmitMode::Stdout),
         "coverage" => Ok(EmitMode::Coverage),
         "checkstyle" => Ok(EmitMode::Checkstyle),
+        "json" => Ok(EmitMode::Json),
         _ => Err(format_err!("Invalid value for `--emit`")),
     }
 }