]> 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 520a6e66fcd87f954b47352bd358810634833081..753840e065c968ff496c3d6dfba141e1e6e578cd 100644 (file)
@@ -1,14 +1,12 @@
 #![deny(rust_2018_idioms)]
+#![warn(unreachable_pub)]
 
 #[macro_use]
 extern crate derive_new;
-#[cfg(test)]
 #[macro_use]
 extern crate lazy_static;
 #[macro_use]
 extern crate log;
-#[macro_use]
-extern crate serde_derive;
 
 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::{
     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 {
@@ -155,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.
@@ -238,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<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(),
-            )?;
-        }
+        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);
@@ -375,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()
@@ -401,7 +323,11 @@ fn format_snippet(snippet: &str, config: &Config) -> Option<FormattedSnippet> {
 /// 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> {
+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 {
@@ -434,7 +360,7 @@ fn enclose_in_main_block(s: &str, config: &Config) -> String {
     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();
 
@@ -443,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 {
@@ -490,17 +416,21 @@ pub struct Session<'b, T: Write> {
     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(),
         }
@@ -509,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
@@ -556,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);
         }
     }
 }
@@ -571,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)]
@@ -595,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
     }
 
@@ -625,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]