]> git.lizzy.rs Git - rust.git/blobdiff - src/lib.rs
fix: don't force a newline after an empty where clause
[rust.git] / src / lib.rs
index 6fc3dc859455d371010e3a8470839d9ec8209c8d..753840e065c968ff496c3d6dfba141e1e6e578cd 100644 (file)
@@ -1,38 +1,12 @@
-// Copyright 2015-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.
+#![deny(rust_2018_idioms)]
+#![warn(unreachable_pub)]
 
 #[macro_use]
 extern crate derive_new;
-extern crate atty;
-extern crate bytecount;
-extern crate diff;
-extern crate dirs;
-extern crate failure;
-extern crate itertools;
-#[cfg(test)]
 #[macro_use]
 extern crate lazy_static;
 #[macro_use]
 extern crate log;
-extern crate regex;
-extern crate rustc_target;
-extern crate serde;
-#[macro_use]
-extern crate serde_derive;
-extern crate serde_json;
-extern crate syntax;
-extern crate syntax_pos;
-extern crate toml;
-extern crate unicode_categories;
-extern crate unicode_segmentation;
-extern crate unicode_width;
 
 use std::cell::RefCell;
 use std::collections::HashMap;
 use std::path::PathBuf;
 use std::rc::Rc;
 
-use failure::Fail;
-use syntax::ast;
+use ignore;
+use rustc_ast::ast;
+use rustc_span::symbol;
+use thiserror::Error;
 
 use crate::comment::LineClasses;
+use crate::emitter::Emitter;
 use crate::formatting::{FormatErrorMap, FormattingError, ReportedErrors, SourceFile};
 use crate::issues::Issue;
+use crate::modules::ModuleResolutionError;
 use crate::shape::Indent;
+use crate::syntux::parser::DirectoryOwnership;
+use crate::utils::indent_next_line;
 
 pub use crate::config::{
     load_config, CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName, NewlineStyle,
     Range, Verbosity,
 };
 
+pub use crate::format_report_formatter::{FormatReportFormatter, FormatReportFormatterBuilder};
+
+pub use crate::rustfmt_diff::{ModifiedChunk, ModifiedLines};
+
 #[macro_use]
 mod utils;
 
 mod attr;
 mod chains;
-pub(crate) mod checkstyle;
 mod closures;
 mod comment;
 pub(crate) mod config;
+mod coverage;
+mod emitter;
 mod expr;
+mod format_report_formatter;
 pub(crate) mod formatting;
+mod ignore_path;
 mod imports;
 mod issues;
 mod items;
 mod overflow;
 mod pairs;
 mod patterns;
+mod release_channel;
 mod reorder;
 mod rewrite;
 pub(crate) mod rustfmt_diff;
 mod shape;
+mod skip;
 pub(crate) mod source_file;
 pub(crate) mod source_map;
 mod spanned;
+mod stmt;
 mod string;
+mod syntux;
 #[cfg(test)]
 mod test;
 mod types;
 
 /// The various errors that can occur during formatting. Note that not all of
 /// these can currently be propagated to clients.
-#[derive(Fail, Debug)]
+#[derive(Error, Debug)]
 pub enum ErrorKind {
     /// Line has exceeded character limit (found, maximum).
-    #[fail(
-        display = "line formatted, but exceeded maximum width \
-                   (maximum: {} (see `max_width` option), found: {})",
-        _1, _0
+    #[error(
+        "line formatted, but exceeded maximum width \
+         (maximum: {1} (see `max_width` option), found: {0})"
     )]
     LineOverflow(usize, usize),
     /// Line ends in whitespace.
-    #[fail(display = "left behind trailing whitespace")]
+    #[error("left behind trailing whitespace")]
     TrailingWhitespace,
     /// TODO or FIXME item without an issue number.
-    #[fail(display = "found {}", _0)]
+    #[error("found {0}")]
     BadIssue(Issue),
     /// License check has failed.
-    #[fail(display = "license check failed")]
+    #[error("license check failed")]
     LicenseCheck,
     /// Used deprecated skip attribute.
-    #[fail(display = "`rustfmt_skip` is deprecated; use `rustfmt::skip`")]
+    #[error("`rustfmt_skip` is deprecated; use `rustfmt::skip`")]
     DeprecatedAttr,
-    /// Used a rustfmt:: attribute other than skip.
-    #[fail(display = "invalid attribute")]
+    /// Used a rustfmt:: attribute other than skip or skip::macros.
+    #[error("invalid attribute")]
     BadAttr,
     /// An io error during reading or writing.
-    #[fail(display = "io error: {}", _0)]
+    #[error("io error: {0}")]
     IoError(io::Error),
+    /// Error during module resolution.
+    #[error("{0}")]
+    ModuleResolutionError(#[from] ModuleResolutionError),
     /// Parse error occurred when parsing the input.
-    #[fail(display = "parse error")]
+    #[error("parse error")]
     ParseError,
     /// The user mandated a version and the current version of Rustfmt does not
     /// satisfy that requirement.
-    #[fail(display = "version mismatch")]
+    #[error("version mismatch")]
     VersionMismatch,
     /// If we had formatted the given node, then we would have lost a comment.
-    #[fail(display = "not formatted because a comment would be lost")]
+    #[error("not formatted because a comment would be lost")]
     LostComment,
+    /// Invalid glob pattern in `ignore` configuration option.
+    #[error("Invalid glob pattern found in ignore list: {0}")]
+    InvalidGlobPattern(ignore::Error),
 }
 
 impl ErrorKind {
@@ -168,7 +164,7 @@ fn unwrap_code_block(&mut self) {
             });
     }
 
-    /// Returns true if the line n did not get formatted.
+    /// Returns `true` if the line n did not get formatted.
     fn is_line_non_formatted(&self, n: usize) -> bool {
         self.non_formatted_ranges
             .iter()
@@ -178,7 +174,7 @@ fn is_line_non_formatted(&self, n: usize) -> bool {
 
 /// Reports on any issues that occurred during a run of Rustfmt.
 ///
-/// Can be reported to the user via its `Display` implementation of `print_fancy`.
+/// Can be reported to the user using the `Display` impl on [`FormatReportFormatter`].
 #[derive(Clone)]
 pub struct FormatReport {
     // Maps stringified file paths to their associated formatting errors.
@@ -261,133 +257,36 @@ pub fn has_warnings(&self) -> bool {
 
     /// Print the report to a terminal using colours and potentially other
     /// fancy output.
+    #[deprecated(note = "Use FormatReportFormatter with colors enabled instead")]
     pub fn fancy_print(
         &self,
-        mut t: Box<term::Terminal<Output = io::Stderr>>,
+        mut t: Box<dyn term::Terminal<Output = io::Stderr>>,
     ) -> Result<(), term::Error> {
-        for (file, errors) in &self.internal.borrow().0 {
-            for error in errors {
-                let prefix_space_len = error.line.to_string().len();
-                let prefix_spaces = " ".repeat(1 + prefix_space_len);
-
-                // First line: the overview of error
-                t.fg(term::color::RED)?;
-                t.attr(term::Attr::Bold)?;
-                write!(t, "{} ", error.msg_prefix())?;
-                t.reset()?;
-                t.attr(term::Attr::Bold)?;
-                writeln!(t, "{}", error.kind)?;
-
-                // Second line: file info
-                write!(t, "{}--> ", &prefix_spaces[1..])?;
-                t.reset()?;
-                writeln!(t, "{}:{}", file, error.line)?;
-
-                // Third to fifth lines: show the line which triggered error, if available.
-                if !error.line_buffer.is_empty() {
-                    let (space_len, target_len) = error.format_len();
-                    t.attr(term::Attr::Bold)?;
-                    write!(t, "{}|\n{} | ", prefix_spaces, error.line)?;
-                    t.reset()?;
-                    writeln!(t, "{}", error.line_buffer)?;
-                    t.attr(term::Attr::Bold)?;
-                    write!(t, "{}| ", prefix_spaces)?;
-                    t.fg(term::color::RED)?;
-                    writeln!(t, "{}", FormatReport::target_str(space_len, target_len))?;
-                    t.reset()?;
-                }
-
-                // The last line: show note if available.
-                let msg_suffix = error.msg_suffix();
-                if !msg_suffix.is_empty() {
-                    t.attr(term::Attr::Bold)?;
-                    write!(t, "{}= note: ", prefix_spaces)?;
-                    t.reset()?;
-                    writeln!(t, "{}", error.msg_suffix())?;
-                } else {
-                    writeln!(t)?;
-                }
-                t.reset()?;
-            }
-        }
-
-        if !self.internal.borrow().0.is_empty() {
-            t.attr(term::Attr::Bold)?;
-            write!(t, "warning: ")?;
-            t.reset()?;
-            write!(
-                t,
-                "rustfmt may have failed to format. See previous {} errors.\n\n",
-                self.warning_count(),
-            )?;
-        }
-
+        writeln!(
+            t,
+            "{}",
+            FormatReportFormatterBuilder::new(&self)
+                .enable_colors(true)
+                .build()
+        )?;
         Ok(())
     }
-
-    fn target_str(space_len: usize, target_len: usize) -> String {
-        let empty_line = " ".repeat(space_len);
-        let overflowed = "^".repeat(target_len);
-        empty_line + &overflowed
-    }
 }
 
+/// Deprecated - Use FormatReportFormatter instead
+// https://github.com/rust-lang/rust/issues/78625
+// https://github.com/rust-lang/rust/issues/39935
 impl fmt::Display for FormatReport {
     // Prints all the formatting errors.
-    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
-        for (file, errors) in &self.internal.borrow().0 {
-            for error in errors {
-                let prefix_space_len = error.line.to_string().len();
-                let prefix_spaces = " ".repeat(1 + prefix_space_len);
-
-                let error_line_buffer = if error.line_buffer.is_empty() {
-                    String::from(" ")
-                } else {
-                    let (space_len, target_len) = error.format_len();
-                    format!(
-                        "{}|\n{} | {}\n{}| {}",
-                        prefix_spaces,
-                        error.line,
-                        error.line_buffer,
-                        prefix_spaces,
-                        FormatReport::target_str(space_len, target_len)
-                    )
-                };
-
-                let error_info = format!("{} {}", error.msg_prefix(), error.kind);
-                let file_info = format!("{}--> {}:{}", &prefix_spaces[1..], file, error.line);
-                let msg_suffix = error.msg_suffix();
-                let note = if msg_suffix.is_empty() {
-                    String::new()
-                } else {
-                    format!("{}note= ", prefix_spaces)
-                };
-
-                writeln!(
-                    fmt,
-                    "{}\n{}\n{}\n{}{}",
-                    error_info,
-                    file_info,
-                    error_line_buffer,
-                    note,
-                    error.msg_suffix()
-                )?;
-            }
-        }
-        if !self.internal.borrow().0.is_empty() {
-            writeln!(
-                fmt,
-                "warning: rustfmt may have failed to format. See previous {} errors.",
-                self.warning_count(),
-            )?;
-        }
+    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        write!(fmt, "{}", FormatReportFormatterBuilder::new(&self).build())?;
         Ok(())
     }
 }
 
 /// Format the given snippet. The snippet is expected to be *complete* code.
 /// When we cannot parse the given snippet, this function returns `None`.
-fn format_snippet(snippet: &str, config: &Config) -> Option<FormattedSnippet> {
+fn format_snippet(snippet: &str, config: &Config, is_macro_def: bool) -> Option<FormattedSnippet> {
     let mut config = config.clone();
     panic::catch_unwind(|| {
         let mut out: Vec<u8> = Vec::with_capacity(snippet.len() * 2);
@@ -398,7 +297,7 @@ fn format_snippet(snippet: &str, config: &Config) -> Option<FormattedSnippet> {
         let (formatting_error, result) = {
             let input = Input::Text(snippet.into());
             let mut session = Session::new(config, Some(&mut out));
-            let result = session.format(input);
+            let result = session.format_input_inner(input, is_macro_def);
             (
                 session.errors.has_macro_format_failure
                     || session.out.as_ref().unwrap().is_empty() && !snippet.is_empty()
@@ -421,10 +320,14 @@ fn format_snippet(snippet: &str, config: &Config) -> Option<FormattedSnippet> {
 }
 
 /// Format the given code block. Mainly targeted for code block in comment.
-/// The code block may be incomplete (i.e. parser may be unable to parse it).
+/// The code block may be incomplete (i.e., parser may be unable to parse it).
 /// To avoid panic in parser, we wrap the code block with a dummy function.
-/// The returned code block does *not* end with newline.
-fn format_code_block(code_snippet: &str, config: &Config) -> Option<FormattedSnippet> {
+/// The returned code block does **not** end with newline.
+fn format_code_block(
+    code_snippet: &str,
+    config: &Config,
+    is_macro_def: bool,
+) -> Option<FormattedSnippet> {
     const FN_MAIN_PREFIX: &str = "fn main() {\n";
 
     fn enclose_in_main_block(s: &str, config: &Config) -> String {
@@ -438,7 +341,7 @@ fn enclose_in_main_block(s: &str, config: &Config) -> String {
             }
             result.push_str(&line);
             result.push('\n');
-            need_indent = !kind.is_string() || line.ends_with('\\');
+            need_indent = indent_next_line(kind, &line, config);
         }
         result.push('}');
         result
@@ -450,14 +353,14 @@ fn enclose_in_main_block(s: &str, config: &Config) -> String {
     let mut is_first = true;
 
     // While formatting the code, ignore the config's newline style setting and always use "\n"
-    // instead of "\r\n" for the newline characters. This is okay because the output here is
+    // instead of "\r\n" for the newline characters. This is ok because the output here is
     // not directly outputted by rustfmt command, but used by the comment formatter's input.
     // We have output-file-wide "\n" ==> "\r\n" conversion process after here if it's necessary.
     let mut config_with_unix_newline = config.clone();
     config_with_unix_newline
         .set()
         .newline_style(NewlineStyle::Unix);
-    let mut formatted = format_snippet(&snippet, &config_with_unix_newline)?;
+    let mut formatted = format_snippet(&snippet, &config_with_unix_newline, is_macro_def)?;
     // Remove wrapping main block
     formatted.unwrap_code_block();
 
@@ -466,7 +369,7 @@ fn enclose_in_main_block(s: &str, config: &Config) -> String {
     let block_len = formatted
         .snippet
         .rfind('}')
-        .unwrap_or(formatted.snippet.len());
+        .unwrap_or_else(|| formatted.snippet.len());
     let mut is_indented = true;
     for (kind, ref line) in LineClasses::new(&formatted.snippet[FN_MAIN_PREFIX.len()..block_len]) {
         if !is_first {
@@ -499,7 +402,7 @@ fn enclose_in_main_block(s: &str, config: &Config) -> String {
             line
         };
         result.push_str(trimmed_line);
-        is_indented = !kind.is_string() || line.ends_with('\\');
+        is_indented = indent_next_line(kind, line, config);
     }
     Some(FormattedSnippet {
         snippet: result,
@@ -508,22 +411,26 @@ fn enclose_in_main_block(s: &str, config: &Config) -> String {
 }
 
 /// A session is a run of rustfmt across a single or multiple inputs.
-pub struct Session<'b, T: Write + 'b> {
+pub struct Session<'b, T: Write> {
     pub config: Config,
     pub out: Option<&'b mut T>,
     pub(crate) errors: ReportedErrors,
     source_file: SourceFile,
+    emitter: Box<dyn Emitter + 'b>,
 }
 
 impl<'b, T: Write + 'b> Session<'b, T> {
-    pub fn new(config: Config, out: Option<&'b mut T>) -> Session<'b, T> {
-        if config.emit_mode() == EmitMode::Checkstyle {
-            println!("{}", checkstyle::header());
+    pub fn new(config: Config, mut out: Option<&'b mut T>) -> Session<'b, T> {
+        let emitter = create_emitter(&config);
+
+        if let Some(ref mut out) = out {
+            let _ = emitter.emit_header(out);
         }
 
         Session {
             config,
             out,
+            emitter,
             errors: ReportedErrors::default(),
             source_file: SourceFile::new(),
         }
@@ -532,7 +439,7 @@ pub fn new(config: Config, out: Option<&'b mut T>) -> Session<'b, T> {
     /// The main entry point for Rustfmt. Formats the given input according to the
     /// given config. `out` is only necessary if required by the configuration.
     pub fn format(&mut self, input: Input) -> Result<FormatReport, ErrorKind> {
-        self.format_input_inner(input)
+        self.format_input_inner(input, false)
     }
 
     pub fn override_config<F, U>(&mut self, mut config: Config, f: F) -> U
@@ -579,10 +486,28 @@ pub fn has_no_errors(&self) -> bool {
     }
 }
 
+pub(crate) fn create_emitter<'a>(config: &Config) -> Box<dyn Emitter + 'a> {
+    match config.emit_mode() {
+        EmitMode::Files if config.make_backup() => {
+            Box::new(emitter::FilesWithBackupEmitter::default())
+        }
+        EmitMode::Files => Box::new(emitter::FilesEmitter::new(
+            config.print_misformatted_file_names(),
+        )),
+        EmitMode::Stdout | EmitMode::Coverage => {
+            Box::new(emitter::StdoutEmitter::new(config.verbose()))
+        }
+        EmitMode::Json => Box::new(emitter::JsonEmitter::default()),
+        EmitMode::ModifiedLines => Box::new(emitter::ModifiedLinesEmitter::default()),
+        EmitMode::Checkstyle => Box::new(emitter::CheckstyleEmitter::default()),
+        EmitMode::Diff => Box::new(emitter::DiffEmitter::new(config.clone())),
+    }
+}
+
 impl<'b, T: Write + 'b> Drop for Session<'b, T> {
     fn drop(&mut self) {
-        if self.config.emit_mode() == EmitMode::Checkstyle {
-            println!("{}", checkstyle::footer());
+        if let Some(ref mut out) = self.out {
+            let _ = self.emitter.emit_footer(out);
         }
     }
 }
@@ -594,19 +519,30 @@ pub enum Input {
 }
 
 impl Input {
-    fn is_text(&self) -> bool {
-        match *self {
-            Input::File(_) => false,
-            Input::Text(_) => true,
-        }
-    }
-
     fn file_name(&self) -> FileName {
         match *self {
             Input::File(ref file) => FileName::Real(file.clone()),
             Input::Text(..) => FileName::Stdin,
         }
     }
+
+    fn to_directory_ownership(&self) -> Option<DirectoryOwnership> {
+        match self {
+            Input::File(ref file) => {
+                // If there exists a directory with the same name as an input,
+                // then the input should be parsed as a sub module.
+                let file_stem = file.file_stem()?;
+                if file.parent()?.to_path_buf().join(file_stem).is_dir() {
+                    Some(DirectoryOwnership::Owned {
+                        relative: file_stem.to_str().map(symbol::Ident::from_str),
+                    })
+                } else {
+                    None
+                }
+            }
+            _ => None,
+        }
+    }
 }
 
 #[cfg(test)]
@@ -618,15 +554,15 @@ fn test_no_panic_on_format_snippet_and_format_code_block() {
         // `format_snippet()` and `format_code_block()` should not panic
         // even when we cannot parse the given snippet.
         let snippet = "let";
-        assert!(format_snippet(snippet, &Config::default()).is_none());
-        assert!(format_code_block(snippet, &Config::default()).is_none());
+        assert!(format_snippet(snippet, &Config::default(), false).is_none());
+        assert!(format_code_block(snippet, &Config::default(), false).is_none());
     }
 
     fn test_format_inner<F>(formatter: F, input: &str, expected: &str) -> bool
     where
-        F: Fn(&str, &Config) -> Option<FormattedSnippet>,
+        F: Fn(&str, &Config, bool) -> Option<FormattedSnippet>,
     {
-        let output = formatter(input, &Config::default());
+        let output = formatter(input, &Config::default(), false);
         output.is_some() && output.unwrap().snippet == expected
     }
 
@@ -648,7 +584,7 @@ fn test_format_snippet() {
     fn test_format_code_block_fail() {
         #[rustfmt::skip]
         let code_block = "this_line_is_100_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(x, y, z);";
-        assert!(format_code_block(code_block, &Config::default()).is_none());
+        assert!(format_code_block(code_block, &Config::default(), false).is_none());
     }
 
     #[test]