]> git.lizzy.rs Git - rust.git/blobdiff - src/formatting.rs
Rollup merge of #86274 - alexander-melentyev:spaces, r=bjorn3
[rust.git] / src / formatting.rs
index d940c35f1c734532b416fef590c2fa5a208f32e9..b69ecdc5cb8ae2985065bf54c5872d0528f1ac69 100644 (file)
@@ -2,37 +2,39 @@
 
 use std::collections::HashMap;
 use std::io::{self, Write};
-use std::panic::{catch_unwind, AssertUnwindSafe};
-use std::rc::Rc;
 use std::time::{Duration, Instant};
 
-use syntax::ast;
-use syntax::errors::emitter::{ColorConfig, EmitterWriter};
-use syntax::errors::Handler;
-use syntax::parse::{self, ParseSess};
-use syntax::source_map::{FilePathMapping, SourceMap, Span};
+use rustc_ast::ast;
+use rustc_span::Span;
 
-use comment::{CharClasses, FullCodeCharKind};
-use config::{Config, FileName, Verbosity};
-use issues::BadIssueSeeker;
-use visitor::{FmtVisitor, SnippetProvider};
-use {modules, source_file, ErrorKind, FormatReport, Input, Session};
+use self::newline_style::apply_newline_style;
+use crate::comment::{CharClasses, FullCodeCharKind};
+use crate::config::{Config, FileName, Verbosity};
+use crate::issues::BadIssueSeeker;
+use crate::modules::Module;
+use crate::syntux::parser::{DirectoryOwnership, Parser, ParserError};
+use crate::syntux::session::ParseSess;
+use crate::utils::count_newlines;
+use crate::visitor::FmtVisitor;
+use crate::{modules, source_file, ErrorKind, FormatReport, Input, Session};
+
+mod newline_style;
 
 // A map of the files of a crate, with their new content
 pub(crate) type SourceFile = Vec<FileRecord>;
 pub(crate) type FileRecord = (FileName, String);
 
 impl<'b, T: Write + 'b> Session<'b, T> {
-    pub(crate) fn format_input_inner(&mut self, input: Input) -> Result<FormatReport, ErrorKind> {
+    pub(crate) fn format_input_inner(
+        &mut self,
+        input: Input,
+        is_macro_def: bool,
+    ) -> Result<FormatReport, ErrorKind> {
         if !self.config.version_meets_requirement() {
             return Err(ErrorKind::VersionMismatch);
         }
 
-        syntax::with_globals(|| {
-            syntax_pos::hygiene::set_default_edition(
-                self.config.edition().to_libsyntax_pos_edition(),
-            );
-
+        rustc_span::with_session_globals(self.config.edition().into(), || {
             if self.config.disable_all_formatting() {
                 // When the input is from stdin, echo back the input.
                 if let Input::Text(ref buf) = input {
@@ -44,14 +46,10 @@ pub(crate) fn format_input_inner(&mut self, input: Input) -> Result<FormatReport
             }
 
             let config = &self.config.clone();
-            let format_result = format_project(input, config, self);
+            let format_result = format_project(input, config, self, is_macro_def);
 
             format_result.map(|report| {
-                {
-                    let new_errors = &report.internal.borrow().1;
-
-                    self.errors.add(new_errors);
-                }
+                self.errors.add(&report.internal.borrow().1);
                 report
             })
         })
@@ -63,33 +61,54 @@ fn format_project<T: FormatHandler>(
     input: Input,
     config: &Config,
     handler: &mut T,
+    is_macro_def: bool,
 ) -> Result<FormatReport, ErrorKind> {
     let mut timer = Timer::start();
 
     let main_file = input.file_name();
     let input_is_stdin = main_file == FileName::Stdin;
 
+    let parse_session = ParseSess::new(config)?;
+    if config.skip_children() && parse_session.ignore_file(&main_file) {
+        return Ok(FormatReport::new());
+    }
+
     // Parse the crate.
-    let source_map = Rc::new(SourceMap::new(FilePathMapping::empty()));
-    let mut parse_session = make_parse_sess(source_map.clone(), config);
     let mut report = FormatReport::new();
-    let krate = parse_crate(input, &parse_session, config, &mut report)?;
+    let directory_ownership = input.to_directory_ownership();
+    let krate = match Parser::parse_crate(input, &parse_session) {
+        Ok(krate) => krate,
+        // Surface parse error via Session (errors are merged there from report)
+        Err(e) => {
+            let forbid_verbose = input_is_stdin || e != ParserError::ParsePanicError;
+            should_emit_verbose(forbid_verbose, config, || {
+                eprintln!("The Rust parser panicked");
+            });
+            report.add_parsing_error();
+            return Ok(report);
+        }
+    };
+
+    let mut context = FormatContext::new(&krate, report, parse_session, config, handler);
+    let files = modules::ModResolver::new(
+        &context.parse_session,
+        directory_ownership.unwrap_or(DirectoryOwnership::UnownedViaBlock),
+        !input_is_stdin && !config.skip_children(),
+    )
+    .visit_crate(&krate)?;
+
     timer = timer.done_parsing();
 
     // Suppress error output if we have to do any further parsing.
-    let silent_emitter = silent_emitter(source_map);
-    parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter);
-
-    let mut context = FormatContext::new(&krate, report, parse_session, config, handler);
+    context.parse_session.set_silent_emitter();
 
-    let files = modules::list_files(&krate, context.parse_session.source_map())?;
     for (path, module) in files {
-        if (config.skip_children() && path != main_file) || config.ignore().skip_file(&path) {
+        let should_ignore = !input_is_stdin && context.ignore_file(&path);
+        if (config.skip_children() && path != main_file) || should_ignore {
             continue;
         }
         should_emit_verbose(input_is_stdin, config, || println!("Formatting {}", path));
-        let is_root = path == main_file;
-        context.format_file(path, module, is_root)?;
+        context.format_file(path, &module, is_macro_def)?;
     }
     timer = timer.done_formatting();
 
@@ -106,7 +125,7 @@ fn format_project<T: FormatHandler>(
 
 // Used for formatting files.
 #[derive(new)]
-struct FormatContext<'a, T: FormatHandler + 'a> {
+struct FormatContext<'a, T: FormatHandler> {
     krate: &'a ast::Crate,
     report: FormatReport,
     parse_session: ParseSess,
@@ -115,44 +134,35 @@ struct FormatContext<'a, T: FormatHandler + 'a> {
 }
 
 impl<'a, T: FormatHandler + 'a> FormatContext<'a, T> {
+    fn ignore_file(&self, path: &FileName) -> bool {
+        self.parse_session.ignore_file(path)
+    }
+
     // Formats a single file/module.
     fn format_file(
         &mut self,
         path: FileName,
-        module: &ast::Mod,
-        is_root: bool,
+        module: &Module<'_>,
+        is_macro_def: bool,
     ) -> Result<(), ErrorKind> {
-        let source_file = self
-            .parse_session
-            .source_map()
-            .lookup_char_pos(module.inner.lo())
-            .file;
-        let big_snippet = source_file.src.as_ref().unwrap();
-        let snippet_provider = SnippetProvider::new(source_file.start_pos, big_snippet);
-        let mut visitor = FmtVisitor::from_source_map(
+        let snippet_provider = self.parse_session.snippet_provider(module.span);
+        let mut visitor = FmtVisitor::from_parse_sess(
             &self.parse_session,
             &self.config,
             &snippet_provider,
             self.report.clone(),
         );
-
-        // Format inner attributes if available.
-        if !self.krate.attrs.is_empty() && is_root {
-            visitor.skip_empty_lines(source_file.end_pos);
-            if visitor.visit_attrs(&self.krate.attrs, ast::AttrStyle::Inner) {
-                visitor.push_rewrite(module.inner, None);
-            } else {
-                visitor.format_separate_mod(module, &*source_file);
-            }
-        } else {
-            visitor.last_pos = source_file.start_pos;
-            visitor.skip_empty_lines(source_file.end_pos);
-            visitor.format_separate_mod(module, &*source_file);
-        };
+        visitor.skip_context.update_with_attrs(&self.krate.attrs);
+        visitor.is_macro_def = is_macro_def;
+        visitor.last_pos = snippet_provider.start_pos();
+        visitor.skip_empty_lines(snippet_provider.end_pos());
+        visitor.format_separate_mod(module, snippet_provider.end_pos());
 
         debug_assert_eq!(
             visitor.line_number,
-            ::utils::count_newlines(&visitor.buffer)
+            count_newlines(&visitor.buffer),
+            "failed in format_file visitor.buffer:\n {:?}",
+            &visitor.buffer
         );
 
         // For some reason, the source_map does not include terminating
@@ -162,20 +172,29 @@ fn format_file(
         format_lines(
             &mut visitor.buffer,
             &path,
-            &visitor.skipped_range,
+            &visitor.skipped_range.borrow(),
             &self.config,
             &self.report,
         );
-        self.config
-            .newline_style()
-            .apply(&mut visitor.buffer, &big_snippet);
+
+        apply_newline_style(
+            self.config.newline_style(),
+            &mut visitor.buffer,
+            snippet_provider.entire_snippet(),
+        );
 
         if visitor.macro_rewrite_failure {
             self.report.add_macro_format_failure();
         }
+        self.report
+            .add_non_formatted_ranges(visitor.skipped_range.borrow().clone());
 
-        self.handler
-            .handle_formatted_file(path, visitor.buffer.to_owned(), &mut self.report)
+        self.handler.handle_formatted_file(
+            &self.parse_session,
+            path,
+            visitor.buffer.to_owned(),
+            &mut self.report,
+        )
     }
 }
 
@@ -183,6 +202,7 @@ fn format_file(
 trait FormatHandler {
     fn handle_formatted_file(
         &mut self,
+        parse_session: &ParseSess,
         path: FileName,
         result: String,
         report: &mut FormatReport,
@@ -193,13 +213,21 @@ impl<'b, T: Write + 'b> FormatHandler for Session<'b, T> {
     // Called for each formatted file.
     fn handle_formatted_file(
         &mut self,
+        parse_session: &ParseSess,
         path: FileName,
         result: String,
         report: &mut FormatReport,
     ) -> Result<(), ErrorKind> {
         if let Some(ref mut out) = self.out {
-            match source_file::write_file(&result, &path, out, &self.config) {
-                Ok(b) if b => report.add_diff(),
+            match source_file::write_file(
+                Some(parse_session),
+                &path,
+                &result,
+                out,
+                &mut *self.emitter,
+                self.config.newline_style(),
+            ) {
+                Ok(ref result) if result.has_diff => report.add_diff(),
                 Err(e) => {
                     // Create a new error with path_str to help users see which files failed
                     let err_msg = format!("{}: {}", path, e);
@@ -225,35 +253,26 @@ pub(crate) struct FormattingError {
 impl FormattingError {
     pub(crate) fn from_span(
         span: Span,
-        source_map: &SourceMap,
+        parse_sess: &ParseSess,
         kind: ErrorKind,
     ) -> FormattingError {
         FormattingError {
-            line: source_map.lookup_char_pos(span.lo()).line,
+            line: parse_sess.line_of_byte_pos(span.lo()),
             is_comment: kind.is_comment(),
             kind,
             is_string: false,
-            line_buffer: source_map
-                .span_to_lines(span)
-                .ok()
-                .and_then(|fl| {
-                    fl.file
-                        .get_line(fl.lines[0].line_index)
-                        .map(|l| l.into_owned())
-                })
-                .unwrap_or_else(String::new),
+            line_buffer: parse_sess.span_to_first_line_string(span),
         }
     }
 
-    pub(crate) fn msg_prefix(&self) -> &str {
+    pub(crate) fn is_internal(&self) -> bool {
         match self.kind {
             ErrorKind::LineOverflow(..)
             | ErrorKind::TrailingWhitespace
             | ErrorKind::IoError(_)
             | ErrorKind::ParseError
-            | ErrorKind::LostComment => "internal error:",
-            ErrorKind::LicenseCheck | ErrorKind::BadAttr | ErrorKind::VersionMismatch => "error:",
-            ErrorKind::BadIssue(_) | ErrorKind::DeprecatedAttr => "warning:",
+            | ErrorKind::LostComment => true,
+            _ => false,
         }
     }
 
@@ -274,7 +293,8 @@ pub(crate) fn format_len(&self) -> (usize, usize) {
             | ErrorKind::DeprecatedAttr
             | ErrorKind::BadIssue(_)
             | ErrorKind::BadAttr
-            | ErrorKind::LostComment => {
+            | ErrorKind::LostComment
+            | ErrorKind::LicenseCheck => {
                 let trailing_ws_start = self
                     .line_buffer
                     .rfind(|c: char| !c.is_whitespace())
@@ -292,9 +312,9 @@ pub(crate) fn format_len(&self) -> (usize, usize) {
 
 pub(crate) type FormatErrorMap = HashMap<FileName, Vec<FormattingError>>;
 
-#[derive(Default, Debug)]
+#[derive(Default, Debug, PartialEq)]
 pub(crate) struct ReportedErrors {
-    // Encountered e.g. an IO error.
+    // Encountered e.g., an IO error.
     pub(crate) has_operational_errors: bool,
 
     // Failed to reformat code because of parsing errors.
@@ -311,39 +331,24 @@ pub(crate) struct ReportedErrors {
 
     /// Formatted code differs from existing code (--check only).
     pub(crate) has_diff: bool,
+
+    /// Formatted code missed something, like lost comments or extra trailing space
+    pub(crate) has_unformatted_code_errors: bool,
 }
 
 impl ReportedErrors {
     /// Combine two summaries together.
-    pub fn add(&mut self, other: &ReportedErrors) {
+    pub(crate) fn add(&mut self, other: &ReportedErrors) {
         self.has_operational_errors |= other.has_operational_errors;
         self.has_parsing_errors |= other.has_parsing_errors;
         self.has_formatting_errors |= other.has_formatting_errors;
         self.has_macro_format_failure |= other.has_macro_format_failure;
         self.has_check_errors |= other.has_check_errors;
         self.has_diff |= other.has_diff;
+        self.has_unformatted_code_errors |= other.has_unformatted_code_errors;
     }
 }
 
-/// A single span of changed lines, with 0 or more removed lines
-/// and a vector of 0 or more inserted lines.
-#[derive(Debug, PartialEq, Eq)]
-pub(crate) struct ModifiedChunk {
-    /// The first to be removed from the original text
-    pub line_number_orig: u32,
-    /// The number of lines which have been replaced
-    pub lines_removed: u32,
-    /// The new lines
-    pub lines: Vec<String>,
-}
-
-/// Set of changed sections of a file.
-#[derive(Debug, PartialEq, Eq)]
-pub(crate) struct ModifiedLines {
-    /// The set of changed chunks.
-    pub chunks: Vec<ModifiedChunk>,
-}
-
 #[derive(Clone, Copy, Debug)]
 enum Timer {
     Disabled,
@@ -408,7 +413,7 @@ fn duration_to_f32(d: Duration) -> f32 {
 }
 
 // Formatting done on a char by char or line by line basis.
-// FIXME(#20) other stuff for parity with make tidy
+// FIXME(#20): other stuff for parity with make tidy.
 fn format_lines(
     text: &mut String,
     name: &FileName,
@@ -439,8 +444,7 @@ struct FormatLines<'a> {
     errors: Vec<FormattingError>,
     issue_seeker: BadIssueSeeker,
     line_buffer: String,
-    // true if the current line contains a string literal.
-    is_string: bool,
+    current_line_contains_string_literal: bool,
     format_line: bool,
     allow_issue_seek: bool,
     config: &'a Config,
@@ -464,7 +468,7 @@ fn new(
             allow_issue_seek: !issue_seeker.is_disabled(),
             issue_seeker,
             line_buffer: String::with_capacity(config.max_width() * 2),
-            is_string: false,
+            current_line_contains_string_literal: false,
             format_line: config.file_lines().contains_line(name, 1),
             config,
         }
@@ -528,7 +532,8 @@ fn new_line(&mut self, kind: FullCodeCharKind) {
                 && !self.is_skipped_line()
                 && self.should_report_error(kind, &error_kind)
             {
-                self.push_err(error_kind, kind.is_comment(), self.is_string);
+                let is_string = self.current_line_contains_string_literal;
+                self.push_err(error_kind, kind.is_comment(), is_string);
             }
         }
 
@@ -541,7 +546,7 @@ fn new_line(&mut self, kind: FullCodeCharKind) {
         self.newline_count += 1;
         self.last_was_space = false;
         self.line_buffer.clear();
-        self.is_string = false;
+        self.current_line_contains_string_literal = false;
     }
 
     fn char(&mut self, c: char, kind: FullCodeCharKind) {
@@ -554,7 +559,7 @@ fn char(&mut self, c: char, kind: FullCodeCharKind) {
         self.last_was_space = c.is_whitespace();
         self.line_buffer.push(c);
         if kind.is_string() {
-            self.is_string = true;
+            self.current_line_contains_string_literal = true;
         }
     }
 
@@ -569,12 +574,14 @@ fn push_err(&mut self, kind: ErrorKind, is_comment: bool, is_string: bool) {
     }
 
     fn should_report_error(&self, char_kind: FullCodeCharKind, error_kind: &ErrorKind) -> bool {
-        let allow_error_report =
-            if char_kind.is_comment() || self.is_string || error_kind.is_comment() {
-                self.config.error_on_unformatted()
-            } else {
-                true
-            };
+        let allow_error_report = if char_kind.is_comment()
+            || self.current_line_contains_string_literal
+            || error_kind.is_comment()
+        {
+            self.config.error_on_unformatted()
+        } else {
+            true
+        };
 
         match error_kind {
             ErrorKind::LineOverflow(..) => {
@@ -585,7 +592,7 @@ fn should_report_error(&self, char_kind: FullCodeCharKind, error_kind: &ErrorKin
         }
     }
 
-    /// Returns true if the line with the given line number was skipped by `#[rustfmt::skip]`.
+    /// Returns `true` if the line with the given line number was skipped by `#[rustfmt::skip]`.
     fn is_skipped_line(&self) -> bool {
         self.skipped_range
             .iter()
@@ -593,83 +600,11 @@ fn is_skipped_line(&self) -> bool {
     }
 }
 
-fn parse_crate(
-    input: Input,
-    parse_session: &ParseSess,
-    config: &Config,
-    report: &mut FormatReport,
-) -> Result<ast::Crate, ErrorKind> {
-    let input_is_stdin = input.is_text();
-
-    let mut parser = match input {
-        Input::File(file) => parse::new_parser_from_file(parse_session, &file),
-        Input::Text(text) => parse::new_parser_from_source_str(
-            parse_session,
-            syntax::source_map::FileName::Custom("stdin".to_owned()),
-            text,
-        ),
-    };
-
-    parser.cfg_mods = false;
-    if config.skip_children() {
-        parser.recurse_into_file_modules = false;
-    }
-
-    let mut parser = AssertUnwindSafe(parser);
-    let result = catch_unwind(move || parser.0.parse_crate_mod());
-
-    match result {
-        Ok(Ok(c)) => {
-            if !parse_session.span_diagnostic.has_errors() {
-                return Ok(c);
-            }
-        }
-        Ok(Err(mut e)) => e.emit(),
-        Err(_) => {
-            // Note that if you see this message and want more information,
-            // then run the `parse_crate_mod` function above without
-            // `catch_unwind` so rustfmt panics and you can get a backtrace.
-            should_emit_verbose(input_is_stdin, config, || {
-                println!("The Rust parser panicked")
-            });
-        }
-    }
-
-    report.add_parsing_error();
-    Err(ErrorKind::ParseError)
-}
-
-fn silent_emitter(source_map: Rc<SourceMap>) -> Box<EmitterWriter> {
-    Box::new(EmitterWriter::new(
-        Box::new(Vec::new()),
-        Some(source_map),
-        false,
-        false,
-    ))
-}
-
-fn make_parse_sess(source_map: Rc<SourceMap>, config: &Config) -> ParseSess {
-    let tty_handler = if config.hide_parse_errors() {
-        let silent_emitter = silent_emitter(source_map.clone());
-        Handler::with_emitter(true, false, silent_emitter)
-    } else {
-        let supports_color = term::stderr().map_or(false, |term| term.supports_color());
-        let color_cfg = if supports_color {
-            ColorConfig::Auto
-        } else {
-            ColorConfig::Never
-        };
-        Handler::with_tty_emitter(color_cfg, true, false, Some(source_map.clone()))
-    };
-
-    ParseSess::with_span_handler(tty_handler, source_map)
-}
-
-fn should_emit_verbose<F>(is_stdin: bool, config: &Config, f: F)
+fn should_emit_verbose<F>(forbid_verbose_output: bool, config: &Config, f: F)
 where
     F: Fn(),
 {
-    if config.verbose() == Verbosity::Verbose && !is_stdin {
+    if config.verbose() == Verbosity::Verbose && !forbid_verbose_output {
         f();
     }
 }