]> git.lizzy.rs Git - rust.git/blobdiff - src/formatting.rs
Change `print_diff` to output the correct line number.
[rust.git] / src / formatting.rs
index 71ebe9d41026756e7ec9a4eb241b8176582ca4a5..9386302c479e7eff23f39775f51bf534aa4b4439 100644 (file)
 use std::time::{Duration, Instant};
 
 use syntax::ast;
-use syntax::codemap::{CodeMap, FilePathMapping, Span};
 use syntax::errors::emitter::{ColorConfig, EmitterWriter};
-use syntax::errors::{DiagnosticBuilder, Handler};
+use syntax::errors::Handler;
 use syntax::parse::{self, ParseSess};
+use syntax::source_map::{FilePathMapping, SourceMap, Span};
 
 use comment::{CharClasses, FullCodeCharKind};
-use config::{Config, FileName, NewlineStyle, Verbosity};
+use config::{Config, FileName, Verbosity};
 use issues::BadIssueSeeker;
 use visitor::{FmtVisitor, SnippetProvider};
-use {filemap, modules, ErrorKind, FormatReport, Input, Session};
+use {modules, source_file, ErrorKind, FormatReport, Input, Session};
 
 // A map of the files of a crate, with their new content
-pub(crate) type FileMap = Vec<FileRecord>;
+pub(crate) type SourceFile = Vec<FileRecord>;
 pub(crate) type FileRecord = (FileName, String);
 
-pub(crate) struct FormattingError {
-    pub(crate) line: usize,
-    pub(crate) kind: ErrorKind,
-    is_comment: bool,
-    is_string: bool,
-    pub(crate) line_buffer: String,
-}
-
-impl FormattingError {
-    pub(crate) fn from_span(span: &Span, codemap: &CodeMap, kind: ErrorKind) -> FormattingError {
-        FormattingError {
-            line: codemap.lookup_char_pos(span.lo()).line,
-            is_comment: kind.is_comment(),
-            kind,
-            is_string: false,
-            line_buffer: codemap
-                .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()),
-        }
-    }
-
-    pub(crate) fn msg_prefix(&self) -> &str {
-        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:",
-        }
-    }
-
-    pub(crate) fn msg_suffix(&self) -> &str {
-        if self.is_comment || self.is_string {
-            "set `error_on_unformatted = false` to suppress \
-             the warning against comments or string literals\n"
-        } else {
-            ""
-        }
-    }
-
-    // (space, target)
-    pub(crate) fn format_len(&self) -> (usize, usize) {
-        match self.kind {
-            ErrorKind::LineOverflow(found, max) => (max, found - max),
-            ErrorKind::TrailingWhitespace
-            | ErrorKind::DeprecatedAttr
-            | ErrorKind::BadAttr
-            | ErrorKind::LostComment => {
-                let trailing_ws_start = self
-                    .line_buffer
-                    .rfind(|c: char| !c.is_whitespace())
-                    .map(|pos| pos + 1)
-                    .unwrap_or(0);
-                (
-                    trailing_ws_start,
-                    self.line_buffer.len() - trailing_ws_start,
-                )
-            }
-            _ => unreachable!(),
-        }
-    }
-}
-
-pub(crate) type FormatErrorMap = HashMap<FileName, Vec<FormattingError>>;
-
-#[derive(Default, Debug)]
-pub(crate) struct ReportedErrors {
-    pub(crate) has_operational_errors: bool,
-    pub(crate) has_check_errors: bool,
-}
-
-fn should_emit_verbose<F>(is_stdin: bool, config: &Config, f: F)
-where
-    F: Fn(),
-{
-    if config.verbose() == Verbosity::Verbose && !is_stdin {
-        f();
-    }
-}
-
-/// Returns true if the line with the given line number was skipped by `#[rustfmt::skip]`.
-fn is_skipped_line(line_number: usize, skipped_range: &[(usize, usize)]) -> bool {
-    skipped_range
-        .iter()
-        .any(|&(lo, hi)| lo <= line_number && line_number <= hi)
-}
-
-fn should_report_error(
-    config: &Config,
-    char_kind: FullCodeCharKind,
-    is_string: bool,
-    error_kind: &ErrorKind,
-) -> bool {
-    let allow_error_report = if char_kind.is_comment() || is_string || error_kind.is_comment() {
-        config.error_on_unformatted()
-    } else {
-        true
-    };
-
-    match error_kind {
-        ErrorKind::LineOverflow(..) => config.error_on_line_overflow() && allow_error_report,
-        ErrorKind::TrailingWhitespace | ErrorKind::LostComment => allow_error_report,
-        _ => true,
-    }
-}
-
-// Formatting done on a char by char or line by line basis.
-// FIXME(#20) other stuff for parity with make tidy
-fn format_lines(
-    text: &mut String,
-    name: &FileName,
-    skipped_range: &[(usize, usize)],
-    config: &Config,
-    report: &FormatReport,
-) {
-    let mut last_was_space = false;
-    let mut line_len = 0;
-    let mut cur_line = 1;
-    let mut newline_count = 0;
-    let mut errors = vec![];
-    let mut issue_seeker = BadIssueSeeker::new(config.report_todo(), config.report_fixme());
-    let mut line_buffer = String::with_capacity(config.max_width() * 2);
-    let mut is_string = false; // true if the current line contains a string literal.
-    let mut format_line = config.file_lines().contains_line(name, cur_line);
-    let allow_issue_seek = !issue_seeker.is_disabled();
-
-    // Check license.
-    if let Some(ref license_template) = config.license_template {
-        if !license_template.is_match(text) {
-            errors.push(FormattingError {
-                line: cur_line,
-                kind: ErrorKind::LicenseCheck,
-                is_comment: false,
-                is_string: false,
-                line_buffer: String::new(),
-            });
-        }
-    }
-
-    // Iterate over the chars in the file map.
-    for (kind, c) in CharClasses::new(text.chars()) {
-        if c == '\r' {
-            continue;
-        }
-
-        if allow_issue_seek && format_line {
-            // Add warnings for bad todos/ fixmes
-            if let Some(issue) = issue_seeker.inspect(c) {
-                errors.push(FormattingError {
-                    line: cur_line,
-                    kind: ErrorKind::BadIssue(issue),
-                    is_comment: false,
-                    is_string: false,
-                    line_buffer: String::new(),
-                });
-            }
-        }
-
-        if c == '\n' {
-            if format_line {
-                // Check for (and record) trailing whitespace.
-                if last_was_space {
-                    if should_report_error(config, kind, is_string, &ErrorKind::TrailingWhitespace)
-                        && !is_skipped_line(cur_line, skipped_range)
-                    {
-                        errors.push(FormattingError {
-                            line: cur_line,
-                            kind: ErrorKind::TrailingWhitespace,
-                            is_comment: kind.is_comment(),
-                            is_string: kind.is_string(),
-                            line_buffer: line_buffer.clone(),
-                        });
-                    }
-                    line_len -= 1;
-                }
-
-                // Check for any line width errors we couldn't correct.
-                let error_kind = ErrorKind::LineOverflow(line_len, config.max_width());
-                if line_len > config.max_width()
-                    && !is_skipped_line(cur_line, skipped_range)
-                    && should_report_error(config, kind, is_string, &error_kind)
-                {
-                    errors.push(FormattingError {
-                        line: cur_line,
-                        kind: error_kind,
-                        is_comment: kind.is_comment(),
-                        is_string,
-                        line_buffer: line_buffer.clone(),
-                    });
-                }
-            }
-
-            line_len = 0;
-            cur_line += 1;
-            format_line = config.file_lines().contains_line(name, cur_line);
-            newline_count += 1;
-            last_was_space = false;
-            line_buffer.clear();
-            is_string = false;
-        } else {
-            newline_count = 0;
-            line_len += if c == '\t' { config.tab_spaces() } else { 1 };
-            last_was_space = c.is_whitespace();
-            line_buffer.push(c);
-            if kind.is_string() {
-                is_string = true;
-            }
-        }
-    }
-
-    if newline_count > 1 {
-        debug!("track truncate: {} {}", text.len(), newline_count);
-        let line = text.len() - newline_count + 1;
-        text.truncate(line);
-    }
-
-    report.append(name.clone(), errors);
-}
-
-fn parse_input<'sess>(
-    input: Input,
-    parse_session: &'sess ParseSess,
-    config: &Config,
-) -> Result<ast::Crate, ParseError<'sess>> {
-    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::codemap::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() {
-                // Bail out if the parser recovered from an error.
-                Err(ParseError::Recovered)
-            } else {
-                Ok(c)
-            }
-        }
-        Ok(Err(e)) => Err(ParseError::Error(e)),
-        Err(_) => Err(ParseError::Panic),
-    }
-}
-
-/// All the ways that parsing can fail.
-enum ParseError<'sess> {
-    /// There was an error, but the parser recovered.
-    Recovered,
-    /// There was an error (supplied) and parsing failed.
-    Error(DiagnosticBuilder<'sess>),
-    /// The parser panicked.
-    Panic,
-}
-
 impl<'b, T: Write + 'b> Session<'b, T> {
     pub(crate) fn format_input_inner(&mut self, input: Input) -> Result<FormatReport, ErrorKind> {
         if !self.config.version_meets_requirement() {
@@ -319,112 +46,54 @@ 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);
 
-            format_result.map(|(report, summary)| {
-                self.summary.add(summary);
+            format_result.map(|report| {
+                {
+                    let new_errors = &report.internal.borrow().1;
+
+                    self.errors.add(new_errors);
+                }
                 report
             })
         })
     }
 }
 
-// Handle the results of formatting.
-trait FormatHandler {
-    fn handle_formatted_file(&mut self, path: FileName, result: String) -> Result<(), ErrorKind>;
-}
-
-impl<'b, T: Write + 'b> FormatHandler for Session<'b, T> {
-    // Called for each formatted file.
-    fn handle_formatted_file(
-        &mut self,
-        path: FileName,
-        mut result: String,
-    ) -> Result<(), ErrorKind> {
-        if let Some(ref mut out) = self.out {
-            match filemap::write_file(&mut result, &path, out, &self.config) {
-                Ok(b) if b => self.summary.add_diff(),
-                Err(e) => {
-                    // Create a new error with path_str to help users see which files failed
-                    let err_msg = format!("{}: {}", path, e);
-                    return Err(io::Error::new(e.kind(), err_msg).into());
-                }
-                _ => {}
-            }
-        }
-
-        self.filemap.push((path, result));
-        Ok(())
-    }
-}
-
 // Format an entire crate (or subset of the module tree).
 fn format_project<T: FormatHandler>(
     input: Input,
     config: &Config,
     handler: &mut T,
-) -> Result<(FormatReport, Summary), ErrorKind> {
-    let mut summary = Summary::default();
+) -> Result<FormatReport, ErrorKind> {
     let mut timer = Timer::Initialized(Instant::now());
 
-    let input_is_stdin = input.is_text();
-    let main_file = match input {
-        Input::File(ref file) => FileName::Real(file.clone()),
-        Input::Text(..) => FileName::Stdin,
-    };
+    let main_file = input.file_name();
+    let input_is_stdin = main_file == FileName::Stdin;
 
     // Parse the crate.
-    let codemap = Rc::new(CodeMap::new(FilePathMapping::empty()));
-    let mut parse_session = make_parse_sess(codemap.clone(), config);
-    let krate = match parse_input(input, &parse_session, config) {
-        Ok(krate) => krate,
-        Err(err) => {
-            match err {
-                ParseError::Error(mut diagnostic) => diagnostic.emit(),
-                ParseError::Panic => {
-                    // Note that if you see this message and want more information,
-                    // then go to `parse_input` and run the parse function without
-                    // `catch_unwind` so rustfmt panics and you can get a backtrace.
-                    should_emit_verbose(!input_is_stdin, config, || {
-                        println!("The Rust parser panicked")
-                    });
-                }
-                ParseError::Recovered => {}
-            }
-            summary.add_parsing_error();
-            return Err(ErrorKind::ParseError);
-        }
-    };
+    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)?;
     timer = timer.done_parsing();
 
     // Suppress error output if we have to do any further parsing.
-    let silent_emitter = Box::new(EmitterWriter::new(
-        Box::new(Vec::new()),
-        Some(codemap.clone()),
-        false,
-        false,
-    ));
+    let silent_emitter = silent_emitter(source_map);
     parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter);
 
-    let mut context = FormatContext::new(
-        &krate,
-        FormatReport::new(),
-        summary,
-        parse_session,
-        config,
-        handler,
-    );
+    let mut context = FormatContext::new(&krate, report, parse_session, config, handler);
 
-    let files = modules::list_files(&krate, context.parse_session.codemap())?;
+    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) {
             continue;
         }
-        should_emit_verbose(!input_is_stdin, config, || println!("Formatting {}", path));
+        should_emit_verbose(input_is_stdin, config, || println!("Formatting {}", path));
         let is_root = path == main_file;
         context.format_file(path, module, is_root)?;
     }
     timer = timer.done_formatting();
 
-    should_emit_verbose(!input_is_stdin, config, || {
+    should_emit_verbose(input_is_stdin, config, || {
         println!(
             "Spent {0:.3} secs in the parsing phase, and {1:.3} secs in the formatting phase",
             timer.get_parse_time(),
@@ -432,20 +101,7 @@ fn format_project<T: FormatHandler>(
         )
     });
 
-    if context.report.has_warnings() {
-        context.summary.add_formatting_error();
-    }
-    {
-        let report_errs = &context.report.internal.borrow().1;
-        if report_errs.has_check_errors {
-            context.summary.add_check_error();
-        }
-        if report_errs.has_operational_errors {
-            context.summary.add_operational_error();
-        }
-    }
-
-    Ok((context.report, context.summary))
+    Ok(context.report)
 }
 
 // Used for formatting files.
@@ -453,7 +109,6 @@ fn format_project<T: FormatHandler>(
 struct FormatContext<'a, T: FormatHandler + 'a> {
     krate: &'a ast::Crate,
     report: FormatReport,
-    summary: Summary,
     parse_session: ParseSess,
     config: &'a Config,
     handler: &'a mut T,
@@ -467,31 +122,32 @@ fn format_file(
         module: &ast::Mod,
         is_root: bool,
     ) -> Result<(), ErrorKind> {
-        let filemap = self
+        let source_file = self
             .parse_session
-            .codemap()
+            .source_map()
             .lookup_char_pos(module.inner.lo())
             .file;
-        let big_snippet = filemap.src.as_ref().unwrap();
-        let snippet_provider = SnippetProvider::new(filemap.start_pos, big_snippet);
-        let mut visitor = FmtVisitor::from_codemap(
+        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(
             &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(filemap.end_pos);
+            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, &*filemap);
+                visitor.format_separate_mod(module, &*source_file);
             }
         } else {
-            visitor.last_pos = filemap.start_pos;
-            visitor.skip_empty_lines(filemap.end_pos);
-            visitor.format_separate_mod(module, &*filemap);
+            visitor.last_pos = source_file.start_pos;
+            visitor.skip_empty_lines(source_file.end_pos);
+            visitor.format_separate_mod(module, &*source_file);
         };
 
         debug_assert_eq!(
@@ -499,9 +155,9 @@ fn format_file(
             ::utils::count_newlines(&visitor.buffer)
         );
 
-        // For some reason, the codemap does not include terminating
+        // For some reason, the source_map does not include terminating
         // newlines so we must add one on for each file. This is sad.
-        filemap::append_newline(&mut visitor.buffer);
+        source_file::append_newline(&mut visitor.buffer);
 
         format_lines(
             &mut visitor.buffer,
@@ -510,63 +166,160 @@ fn format_file(
             &self.config,
             &self.report,
         );
-        replace_with_system_newlines(&mut visitor.buffer, &self.config);
+        self.config
+            .newline_style()
+            .apply(&mut visitor.buffer, &big_snippet);
 
         if visitor.macro_rewrite_failure {
-            self.summary.add_macro_format_failure();
+            self.report.add_macro_format_failure();
         }
 
-        self.handler.handle_formatted_file(path, visitor.buffer)
+        self.handler
+            .handle_formatted_file(path, visitor.buffer, &mut self.report)
     }
 }
 
-fn make_parse_sess(codemap: Rc<CodeMap>, config: &Config) -> ParseSess {
-    let tty_handler = if config.hide_parse_errors() {
-        let silent_emitter = Box::new(EmitterWriter::new(
-            Box::new(Vec::new()),
-            Some(codemap.clone()),
-            false,
-            false,
-        ));
-        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(codemap.clone()))
-    };
+// Handle the results of formatting.
+trait FormatHandler {
+    fn handle_formatted_file(
+        &mut self,
+        path: FileName,
+        result: String,
+        report: &mut FormatReport,
+    ) -> Result<(), ErrorKind>;
+}
+
+impl<'b, T: Write + 'b> FormatHandler for Session<'b, T> {
+    // Called for each formatted file.
+    fn handle_formatted_file(
+        &mut self,
+        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(),
+                Err(e) => {
+                    // Create a new error with path_str to help users see which files failed
+                    let err_msg = format!("{}: {}", path, e);
+                    return Err(io::Error::new(e.kind(), err_msg).into());
+                }
+                _ => {}
+            }
+        }
 
-    ParseSess::with_span_handler(tty_handler, codemap)
+        self.source_file.push((path, result));
+        Ok(())
+    }
+}
+
+pub(crate) struct FormattingError {
+    pub(crate) line: usize,
+    pub(crate) kind: ErrorKind,
+    is_comment: bool,
+    is_string: bool,
+    pub(crate) line_buffer: String,
 }
 
-fn replace_with_system_newlines(text: &mut String, config: &Config) -> () {
-    let style = if config.newline_style() == NewlineStyle::Native {
-        if cfg!(windows) {
-            NewlineStyle::Windows
+impl FormattingError {
+    pub(crate) fn from_span(
+        span: Span,
+        source_map: &SourceMap,
+        kind: ErrorKind,
+    ) -> FormattingError {
+        FormattingError {
+            line: source_map.lookup_char_pos(span.lo()).line,
+            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),
+        }
+    }
+
+    pub(crate) fn msg_prefix(&self) -> &str {
+        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:",
+        }
+    }
+
+    pub(crate) fn msg_suffix(&self) -> &str {
+        if self.is_comment || self.is_string {
+            "set `error_on_unformatted = false` to suppress \
+             the warning against comments or string literals\n"
         } else {
-            NewlineStyle::Unix
+            ""
         }
-    } else {
-        config.newline_style()
-    };
+    }
 
-    match style {
-        NewlineStyle::Unix => return,
-        NewlineStyle::Windows => {
-            let mut transformed = String::with_capacity(text.capacity());
-            for c in text.chars() {
-                match c {
-                    '\n' => transformed.push_str("\r\n"),
-                    '\r' => continue,
-                    c => transformed.push(c),
-                }
+    // (space, target)
+    pub(crate) fn format_len(&self) -> (usize, usize) {
+        match self.kind {
+            ErrorKind::LineOverflow(found, max) => (max, found - max),
+            ErrorKind::TrailingWhitespace
+            | ErrorKind::DeprecatedAttr
+            | ErrorKind::BadAttr
+            | ErrorKind::LostComment => {
+                let trailing_ws_start = self
+                    .line_buffer
+                    .rfind(|c: char| !c.is_whitespace())
+                    .map(|pos| pos + 1)
+                    .unwrap_or(0);
+                (
+                    trailing_ws_start,
+                    self.line_buffer.len() - trailing_ws_start,
+                )
             }
-            *text = transformed;
+            _ => unreachable!(),
         }
-        NewlineStyle::Native => unreachable!(),
+    }
+}
+
+pub(crate) type FormatErrorMap = HashMap<FileName, Vec<FormattingError>>;
+
+#[derive(Default, Debug)]
+pub(crate) struct ReportedErrors {
+    // Encountered e.g. an IO error.
+    pub(crate) has_operational_errors: bool,
+
+    // Failed to reformat code because of parsing errors.
+    pub(crate) has_parsing_errors: bool,
+
+    // Code is valid, but it is impossible to format it properly.
+    pub(crate) has_formatting_errors: bool,
+
+    // Code contains macro call that was unable to format.
+    pub(crate) has_macro_format_failure: bool,
+
+    // Failed a check, such as the license check or other opt-in checking.
+    pub(crate) has_check_errors: bool,
+
+    /// Formatted code differs from existing code (--check only).
+    pub(crate) has_diff: bool,
+}
+
+impl ReportedErrors {
+    /// Combine two summaries together.
+    pub 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;
     }
 }
 
@@ -640,87 +393,269 @@ fn duration_to_f32(d: Duration) -> f32 {
     }
 }
 
-/// A summary of a Rustfmt run.
-#[derive(Debug, Default, Clone, Copy)]
-pub struct Summary {
-    // Encountered e.g. an IO error.
-    has_operational_errors: bool,
-
-    // Failed to reformat code because of parsing errors.
-    has_parsing_errors: bool,
-
-    // Code is valid, but it is impossible to format it properly.
-    has_formatting_errors: bool,
+// Formatting done on a char by char or line by line basis.
+// FIXME(#20) other stuff for parity with make tidy
+fn format_lines(
+    text: &mut String,
+    name: &FileName,
+    skipped_range: &[(usize, usize)],
+    config: &Config,
+    report: &FormatReport,
+) {
+    let mut formatter = FormatLines::new(name, skipped_range, config);
+    formatter.check_license(text);
+    formatter.iterate(text);
 
-    // Code contains macro call that was unable to format.
-    pub(crate) has_macro_format_failure: bool,
+    if formatter.newline_count > 1 {
+        debug!("track truncate: {} {}", text.len(), formatter.newline_count);
+        let line = text.len() - formatter.newline_count + 1;
+        text.truncate(line);
+    }
 
-    // Failed a check, such as the license check or other opt-in checking.
-    has_check_errors: bool,
+    report.append(name.clone(), formatter.errors);
+}
 
-    /// Formatted code differs from existing code (--check only).
-    pub has_diff: bool,
+struct FormatLines<'a> {
+    name: &'a FileName,
+    skipped_range: &'a [(usize, usize)],
+    last_was_space: bool,
+    line_len: usize,
+    cur_line: usize,
+    newline_count: usize,
+    errors: Vec<FormattingError>,
+    issue_seeker: BadIssueSeeker,
+    line_buffer: String,
+    // true if the current line contains a string literal.
+    is_string: bool,
+    format_line: bool,
+    allow_issue_seek: bool,
+    config: &'a Config,
 }
 
-impl Summary {
-    pub fn has_operational_errors(&self) -> bool {
-        self.has_operational_errors
+impl<'a> FormatLines<'a> {
+    fn new(
+        name: &'a FileName,
+        skipped_range: &'a [(usize, usize)],
+        config: &'a Config,
+    ) -> FormatLines<'a> {
+        let issue_seeker = BadIssueSeeker::new(config.report_todo(), config.report_fixme());
+        FormatLines {
+            name,
+            skipped_range,
+            last_was_space: false,
+            line_len: 0,
+            cur_line: 1,
+            newline_count: 0,
+            errors: vec![],
+            allow_issue_seek: !issue_seeker.is_disabled(),
+            issue_seeker,
+            line_buffer: String::with_capacity(config.max_width() * 2),
+            is_string: false,
+            format_line: config.file_lines().contains_line(name, 1),
+            config,
+        }
     }
 
-    pub fn has_parsing_errors(&self) -> bool {
-        self.has_parsing_errors
+    fn check_license(&mut self, text: &mut String) {
+        if let Some(ref license_template) = self.config.license_template {
+            if !license_template.is_match(text) {
+                self.errors.push(FormattingError {
+                    line: self.cur_line,
+                    kind: ErrorKind::LicenseCheck,
+                    is_comment: false,
+                    is_string: false,
+                    line_buffer: String::new(),
+                });
+            }
+        }
     }
 
-    pub fn has_formatting_errors(&self) -> bool {
-        self.has_formatting_errors
-    }
+    // Iterate over the chars in the file map.
+    fn iterate(&mut self, text: &mut String) {
+        for (kind, c) in CharClasses::new(text.chars()) {
+            if c == '\r' {
+                continue;
+            }
 
-    pub fn has_check_errors(&self) -> bool {
-        self.has_check_errors
-    }
+            if self.allow_issue_seek && self.format_line {
+                // Add warnings for bad todos/ fixmes
+                if let Some(issue) = self.issue_seeker.inspect(c) {
+                    self.push_err(ErrorKind::BadIssue(issue), false, false);
+                }
+            }
 
-    pub(crate) fn has_macro_formatting_failure(&self) -> bool {
-        self.has_macro_format_failure
+            if c == '\n' {
+                self.new_line(kind);
+            } else {
+                self.char(c, kind);
+            }
+        }
     }
 
-    pub fn add_operational_error(&mut self) {
-        self.has_operational_errors = true;
-    }
+    fn new_line(&mut self, kind: FullCodeCharKind) {
+        if self.format_line {
+            // Check for (and record) trailing whitespace.
+            if self.last_was_space {
+                if self.should_report_error(kind, &ErrorKind::TrailingWhitespace)
+                    && !self.is_skipped_line()
+                {
+                    self.push_err(
+                        ErrorKind::TrailingWhitespace,
+                        kind.is_comment(),
+                        kind.is_string(),
+                    );
+                }
+                self.line_len -= 1;
+            }
 
-    pub(crate) fn add_parsing_error(&mut self) {
-        self.has_parsing_errors = true;
+            // Check for any line width errors we couldn't correct.
+            let error_kind = ErrorKind::LineOverflow(self.line_len, self.config.max_width());
+            if self.line_len > self.config.max_width()
+                && !self.is_skipped_line()
+                && self.should_report_error(kind, &error_kind)
+            {
+                self.push_err(error_kind, kind.is_comment(), self.is_string);
+            }
+        }
+
+        self.line_len = 0;
+        self.cur_line += 1;
+        self.format_line = self
+            .config
+            .file_lines()
+            .contains_line(self.name, self.cur_line);
+        self.newline_count += 1;
+        self.last_was_space = false;
+        self.line_buffer.clear();
+        self.is_string = false;
+    }
+
+    fn char(&mut self, c: char, kind: FullCodeCharKind) {
+        self.newline_count = 0;
+        self.line_len += if c == '\t' {
+            self.config.tab_spaces()
+        } else {
+            1
+        };
+        self.last_was_space = c.is_whitespace();
+        self.line_buffer.push(c);
+        if kind.is_string() {
+            self.is_string = true;
+        }
     }
 
-    pub(crate) fn add_formatting_error(&mut self) {
-        self.has_formatting_errors = true;
+    fn push_err(&mut self, kind: ErrorKind, is_comment: bool, is_string: bool) {
+        self.errors.push(FormattingError {
+            line: self.cur_line,
+            kind,
+            is_comment,
+            is_string,
+            line_buffer: self.line_buffer.clone(),
+        });
     }
 
-    pub(crate) fn add_check_error(&mut self) {
-        self.has_check_errors = true;
+    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
+            };
+
+        match error_kind {
+            ErrorKind::LineOverflow(..) => {
+                self.config.error_on_line_overflow() && allow_error_report
+            }
+            ErrorKind::TrailingWhitespace | ErrorKind::LostComment => allow_error_report,
+            _ => true,
+        }
     }
 
-    pub(crate) fn add_diff(&mut self) {
-        self.has_diff = true;
+    /// 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()
+            .any(|&(lo, hi)| lo <= self.cur_line && self.cur_line <= hi)
     }
+}
+
+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,
+        ),
+    };
 
-    pub(crate) fn add_macro_format_failure(&mut self) {
-        self.has_macro_format_failure = true;
+    parser.cfg_mods = false;
+    if config.skip_children() {
+        parser.recurse_into_file_modules = false;
     }
 
-    pub fn has_no_errors(&self) -> bool {
-        !(self.has_operational_errors
-            || self.has_parsing_errors
-            || self.has_formatting_errors
-            || self.has_diff)
+    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")
+            });
+        }
     }
 
-    /// Combine two summaries together.
-    pub fn add(&mut self, other: Summary) {
-        self.has_operational_errors |= other.has_operational_errors;
-        self.has_formatting_errors |= other.has_formatting_errors;
-        self.has_macro_format_failure |= other.has_macro_format_failure;
-        self.has_parsing_errors |= other.has_parsing_errors;
-        self.has_check_errors |= other.has_check_errors;
-        self.has_diff |= other.has_diff;
+    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)
+where
+    F: Fn(),
+{
+    if config.verbose() == Verbosity::Verbose && !is_stdin {
+        f();
     }
 }