about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorNick Cameron <ncameron@mozilla.com>2018-07-23 14:52:02 +1200
committerNick Cameron <ncameron@mozilla.com>2018-07-23 15:37:34 +1200
commit71d3d04270474ae0afdeeb410fdcc168a54714b7 (patch)
tree3fa5b35afdbddf86bd68a61c43987ab0c24adc74 /src
parent4153e66e422fde262bac3d36b82e79af69d01f2d (diff)
factor out a `Session` object
Diffstat (limited to 'src')
-rw-r--r--src/bin/main.rs163
-rw-r--r--src/formatting.rs278
-rw-r--r--src/git-rustfmt/main.rs12
-rw-r--r--src/imports.rs6
-rw-r--r--src/items.rs2
-rw-r--r--src/lib.rs83
-rw-r--r--src/test/mod.rs111
7 files changed, 334 insertions, 321 deletions
diff --git a/src/bin/main.rs b/src/bin/main.rs
index cd5e58437e6..5b6398f028e 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -28,8 +28,8 @@ use failure::err_msg;
 use getopts::{Matches, Options};
 
 use rustfmt::{
-    checkstyle_footer, checkstyle_header, format_input, load_config, CliOptions, Color, Config,
-    EmitMode, ErrorKind, FileLines, FileName, Input, Summary, Verbosity,
+    load_config, CliOptions, Color, Config, EmitMode, ErrorKind, FileLines, FileName, Input,
+    Session, Verbosity,
 };
 
 fn main() {
@@ -37,17 +37,7 @@ fn main() {
     let opts = make_opts();
 
     let exit_code = match execute(&opts) {
-        Ok((exit_mode, summary)) => {
-            if summary.has_operational_errors()
-                || summary.has_parsing_errors()
-                || ((summary.has_diff || summary.has_check_errors())
-                    && exit_mode == ExitCodeMode::Check)
-            {
-                1
-            } else {
-                0
-            }
-        }
+        Ok(code) => code,
         Err(e) => {
             eprintln!("{}", e.to_string());
             1
@@ -174,26 +164,27 @@ fn is_nightly() -> bool {
         .unwrap_or(false)
 }
 
-fn execute(opts: &Options) -> Result<(ExitCodeMode, Summary), failure::Error> {
+// Returned i32 is an exit code
+fn execute(opts: &Options) -> Result<i32, failure::Error> {
     let matches = opts.parse(env::args().skip(1))?;
     let options = GetOptsOptions::from_matches(&matches)?;
 
     match determine_operation(&matches)? {
         Operation::Help(HelpOp::None) => {
             print_usage_to_stdout(opts, "");
-            Ok((ExitCodeMode::Normal, Summary::default()))
+            return Ok(1);
         }
         Operation::Help(HelpOp::Config) => {
             Config::print_docs(&mut stdout(), options.unstable_features);
-            Ok((ExitCodeMode::Normal, Summary::default()))
+            return Ok(1);
         }
         Operation::Help(HelpOp::FileLines) => {
             print_help_file_lines();
-            Ok((ExitCodeMode::Normal, Summary::default()))
+            return Ok(1);
         }
         Operation::Version => {
             print_version();
-            Ok((ExitCodeMode::Normal, Summary::default()))
+            return Ok(1);
         }
         Operation::ConfigOutputDefault { path } => {
             let toml = Config::default().all_options().to_toml().map_err(err_msg)?;
@@ -203,35 +194,9 @@ fn execute(opts: &Options) -> Result<(ExitCodeMode, Summary), failure::Error> {
             } else {
                 io::stdout().write_all(toml.as_bytes())?;
             }
-            Ok((ExitCodeMode::Normal, Summary::default()))
-        }
-        Operation::Stdin { input } => {
-            // try to read config from local directory
-            let (mut config, _) = load_config(Some(Path::new(".")), Some(options.clone()))?;
-
-            // emit mode is always Stdout for Stdin.
-            config.set().emit_mode(EmitMode::Stdout);
-            config.set().verbose(Verbosity::Quiet);
-
-            // parse file_lines
-            config.set().file_lines(options.file_lines);
-            for f in config.file_lines().files() {
-                match *f {
-                    FileName::Stdin => {}
-                    _ => eprintln!("Warning: Extra file listed in file_lines option '{}'", f),
-                }
-            }
-
-            let mut error_summary = Summary::default();
-            emit_pre_matter(&config);
-            match format_and_emit_report(Input::Text(input), &config) {
-                Ok(summary) => error_summary.add(summary),
-                Err(_) => error_summary.add_operational_error(),
-            }
-            emit_post_matter(&config);
-
-            Ok((ExitCodeMode::Normal, error_summary))
+            return Ok(1);
         }
+        Operation::Stdin { input } => format_string(input, options),
         Operation::Format {
             files,
             minimal_config_path,
@@ -239,11 +204,41 @@ fn execute(opts: &Options) -> Result<(ExitCodeMode, Summary), failure::Error> {
     }
 }
 
+fn format_string(input: String, options: GetOptsOptions) -> Result<i32, failure::Error> {
+    // try to read config from local directory
+    let (mut config, _) = load_config(Some(Path::new(".")), Some(options.clone()))?;
+
+    // emit mode is always Stdout for Stdin.
+    config.set().emit_mode(EmitMode::Stdout);
+    config.set().verbose(Verbosity::Quiet);
+
+    // parse file_lines
+    config.set().file_lines(options.file_lines);
+    for f in config.file_lines().files() {
+        match *f {
+            FileName::Stdin => {}
+            _ => eprintln!("Warning: Extra file listed in file_lines option '{}'", f),
+        }
+    }
+
+    let out = &mut stdout();
+    let mut session = Session::new(config, Some(out));
+    format_and_emit_report(&mut session, Input::Text(input));
+
+    let exit_code =
+        if session.summary.has_operational_errors() || session.summary.has_parsing_errors() {
+            1
+        } else {
+            0
+        };
+    Ok(exit_code)
+}
+
 fn format(
     files: Vec<PathBuf>,
     minimal_config_path: Option<String>,
     options: GetOptsOptions,
-) -> Result<(ExitCodeMode, Summary), failure::Error> {
+) -> Result<i32, failure::Error> {
     options.verify_file_lines(&files);
     let (config, config_path) = load_config(None, Some(options.clone()))?;
 
@@ -253,19 +248,19 @@ fn format(
         }
     }
 
-    emit_pre_matter(&config);
-    let mut error_summary = Summary::default();
+    let out = &mut stdout();
+    let mut session = Session::new(config, Some(out));
 
     for file in files {
         if !file.exists() {
             eprintln!("Error: file `{}` does not exist", file.to_str().unwrap());
-            error_summary.add_operational_error();
+            session.summary.add_operational_error();
         } else if file.is_dir() {
             eprintln!("Error: `{}` is a directory", file.to_str().unwrap());
-            error_summary.add_operational_error();
+            session.summary.add_operational_error();
         } else {
             // Check the file directory if the config-path could not be read or not provided
-            let local_config = if config_path.is_none() {
+            if config_path.is_none() {
                 let (local_config, config_path) =
                     load_config(Some(file.parent().unwrap()), Some(options.clone()))?;
                 if local_config.verbose() == Verbosity::Verbose {
@@ -277,47 +272,42 @@ fn format(
                         );
                     }
                 }
-                local_config
+
+                session.override_config(local_config, |sess| {
+                    format_and_emit_report(sess, Input::File(file))
+                });
             } else {
-                config.clone()
-            };
-
-            match format_and_emit_report(Input::File(file), &local_config) {
-                Ok(summary) => error_summary.add(summary),
-                Err(_) => {
-                    error_summary.add_operational_error();
-                    break;
-                }
+                format_and_emit_report(&mut session, Input::File(file));
             }
         }
     }
-    emit_post_matter(&config);
 
     // If we were given a path via dump-minimal-config, output any options
     // that were used during formatting as TOML.
     if let Some(path) = minimal_config_path {
         let mut file = File::create(path)?;
-        let toml = config.used_options().to_toml().map_err(err_msg)?;
+        let toml = session.config.used_options().to_toml().map_err(err_msg)?;
         file.write_all(toml.as_bytes())?;
     }
 
-    let exit_mode = if options.check {
-        ExitCodeMode::Check
+    let exit_code = if session.summary.has_operational_errors()
+        || session.summary.has_parsing_errors()
+        || ((session.summary.has_diff || session.summary.has_check_errors()) && options.check)
+    {
+        1
     } else {
-        ExitCodeMode::Normal
+        0
     };
-    Ok((exit_mode, error_summary))
+    Ok(exit_code)
 }
 
-fn format_and_emit_report(input: Input, config: &Config) -> Result<Summary, failure::Error> {
-    let out = &mut stdout();
-
-    match format_input(input, config, Some(out)) {
-        Ok((summary, report)) => {
+fn format_and_emit_report<T: Write>(session: &mut Session<T>, input: Input) {
+    match session.format(input) {
+        Ok(report) => {
             if report.has_warnings() {
                 match term::stderr() {
                     Some(ref t)
-                        if config.color().use_colored_tty()
+                        if session.config.color().use_colored_tty()
                             && t.supports_color()
                             && t.supports_attr(term::Attr::Bold) =>
                     {
@@ -329,29 +319,14 @@ fn format_and_emit_report(input: Input, config: &Config) -> Result<Summary, fail
                     _ => eprintln!("{}", report),
                 }
             }
-
-            Ok(summary)
         }
-        Err((msg, mut summary)) => {
+        Err(msg) => {
             eprintln!("Error writing files: {}", msg);
-            summary.add_operational_error();
-            Ok(summary)
+            session.summary.add_operational_error();
         }
     }
 }
 
-fn emit_pre_matter(config: &Config) {
-    if config.emit_mode() == EmitMode::Checkstyle {
-        println!("{}", checkstyle_header());
-    }
-}
-
-fn emit_post_matter(config: &Config) {
-    if config.emit_mode() == EmitMode::Checkstyle {
-        println!("{}", checkstyle_footer());
-    }
-}
-
 fn print_usage_to_stdout(opts: &Options, reason: &str) {
     let sep = if reason.is_empty() {
         String::new()
@@ -456,12 +431,6 @@ fn determine_operation(matches: &Matches) -> Result<Operation, ErrorKind> {
     })
 }
 
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-enum ExitCodeMode {
-    Normal,
-    Check,
-}
-
 const STABLE_EMIT_MODES: [EmitMode; 3] = [EmitMode::Files, EmitMode::Stdout, EmitMode::Diff];
 
 /// Parsed command line options.
diff --git a/src/formatting.rs b/src/formatting.rs
index 95e2b08e3fe..53e673c5e7a 100644
--- a/src/formatting.rs
+++ b/src/formatting.rs
@@ -15,9 +15,8 @@ use syntax::parse::{self, ParseSess};
 use comment::{CharClasses, FullCodeCharKind};
 use issues::BadIssueSeeker;
 use visitor::{FmtVisitor, SnippetProvider};
-use {filemap, modules, ErrorKind, FormatReport, Input};
+use {filemap, modules, ErrorKind, FormatReport, Input, Session};
 
-use config::summary::Summary;
 use config::{Config, FileName, NewlineStyle, Verbosity};
 
 // A map of the files of a crate, with their new content
@@ -365,143 +364,146 @@ enum ParseError<'sess> {
     Panic,
 }
 
-pub(crate) fn format_input_inner<T: Write>(
-    input: Input,
-    config: &Config,
-    mut out: Option<&mut T>,
-) -> Result<(Summary, FileMap, FormatReport), (ErrorKind, Summary)> {
-    syntax_pos::hygiene::set_default_edition(config.edition().to_libsyntax_pos_edition());
-    let mut summary = Summary::default();
-    if config.disable_all_formatting() {
-        // When the input is from stdin, echo back the input.
-        if let Input::Text(ref buf) = input {
-            if let Err(e) = io::stdout().write_all(buf.as_bytes()) {
-                return Err((From::from(e), summary));
+impl<'b, T: Write + 'b> Session<'b, T> {
+    pub(crate) fn format_input_inner(
+        &mut self,
+        input: Input,
+    ) -> Result<(FileMap, FormatReport), ErrorKind> {
+        syntax_pos::hygiene::set_default_edition(self.config.edition().to_libsyntax_pos_edition());
+
+        if self.config.disable_all_formatting() {
+            // When the input is from stdin, echo back the input.
+            if let Input::Text(ref buf) = input {
+                if let Err(e) = io::stdout().write_all(buf.as_bytes()) {
+                    return Err(From::from(e));
+                }
             }
+            return Ok((FileMap::new(), FormatReport::new()));
         }
-        return Ok((summary, FileMap::new(), FormatReport::new()));
-    }
-    let codemap = Rc::new(CodeMap::new(FilePathMapping::empty()));
+        let codemap = Rc::new(CodeMap::new(FilePathMapping::empty()));
+
+        let tty_handler = if self.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()))
+        };
+        let mut parse_session = ParseSess::with_span_handler(tty_handler, codemap.clone());
+
+        let main_file = match input {
+            Input::File(ref file) => FileName::Real(file.clone()),
+            Input::Text(..) => FileName::Stdin,
+        };
+
+        let krate = match parse_input(input, &parse_session, &self.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(&main_file, &self.config, || {
+                            println!("The Rust parser panicked")
+                        });
+                    }
+                    ParseError::Recovered => {}
+                }
+                self.summary.add_parsing_error();
+                return Err(ErrorKind::ParseError);
+            }
+        };
 
-    let tty_handler = if config.hide_parse_errors() {
+        self.summary.mark_parse_time();
+
+        // Suppress error output after parsing.
         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()))
-    };
-    let mut parse_session = ParseSess::with_span_handler(tty_handler, codemap.clone());
-
-    let main_file = match input {
-        Input::File(ref file) => FileName::Real(file.clone()),
-        Input::Text(..) => FileName::Stdin,
-    };
-
-    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(&main_file, config, || {
-                        println!("The Rust parser panicked")
-                    });
+        parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter);
+
+        let report = FormatReport::new();
+
+        let config = &self.config;
+        let out = &mut self.out;
+        let format_result = format_ast(
+            &krate,
+            &mut parse_session,
+            &main_file,
+            config,
+            report.clone(),
+            |file_name, file, skipped_range, report| {
+                // For some reason, the codemap does not include terminating
+                // newlines so we must add one on for each file. This is sad.
+                filemap::append_newline(file);
+
+                format_lines(file, file_name, skipped_range, config, report);
+                replace_with_system_newlines(file, config);
+
+                if let Some(ref mut out) = out {
+                    return filemap::write_file(file, file_name, out, config);
                 }
-                ParseError::Recovered => {}
-            }
-            summary.add_parsing_error();
-            return Err((ErrorKind::ParseError, summary));
-        }
-    };
+                Ok(false)
+            },
+        );
+
+        self.summary.mark_format_time();
 
-    summary.mark_parse_time();
-
-    // Suppress error output after parsing.
-    let silent_emitter = Box::new(EmitterWriter::new(
-        Box::new(Vec::new()),
-        Some(codemap.clone()),
-        false,
-        false,
-    ));
-    parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter);
-
-    let report = FormatReport::new();
-
-    let format_result = format_ast(
-        &krate,
-        &mut parse_session,
-        &main_file,
-        config,
-        report.clone(),
-        |file_name, file, skipped_range, report| {
-            // For some reason, the codemap does not include terminating
-            // newlines so we must add one on for each file. This is sad.
-            filemap::append_newline(file);
-
-            format_lines(file, file_name, skipped_range, config, report);
-            replace_with_system_newlines(file, config);
-
-            if let Some(ref mut out) = out {
-                return filemap::write_file(file, file_name, out, config);
+        should_emit_verbose(&main_file, &self.config, || {
+            fn duration_to_f32(d: Duration) -> f32 {
+                d.as_secs() as f32 + d.subsec_nanos() as f32 / 1_000_000_000f32
             }
-            Ok(false)
-        },
-    );
 
-    summary.mark_format_time();
+            println!(
+                "Spent {0:.3} secs in the parsing phase, and {1:.3} secs in the formatting phase",
+                duration_to_f32(self.summary.get_parse_time().unwrap()),
+                duration_to_f32(self.summary.get_format_time().unwrap()),
+            )
+        });
 
-    should_emit_verbose(&main_file, config, || {
-        fn duration_to_f32(d: Duration) -> f32 {
-            d.as_secs() as f32 + d.subsec_nanos() as f32 / 1_000_000_000f32
+        {
+            let report_errs = &report.internal.borrow().1;
+            if report_errs.has_check_errors {
+                self.summary.add_check_error();
+            }
+            if report_errs.has_operational_errors {
+                self.summary.add_operational_error();
+            }
         }
 
-        println!(
-            "Spent {0:.3} secs in the parsing phase, and {1:.3} secs in the formatting phase",
-            duration_to_f32(summary.get_parse_time().unwrap()),
-            duration_to_f32(summary.get_format_time().unwrap()),
-        )
-    });
-
-    {
-        let report_errs = &report.internal.borrow().1;
-        if report_errs.has_check_errors {
-            summary.add_check_error();
-        }
-        if report_errs.has_operational_errors {
-            summary.add_operational_error();
-        }
-    }
+        match format_result {
+            Ok((file_map, has_diff, has_macro_rewrite_failure)) => {
+                if report.has_warnings() {
+                    self.summary.add_formatting_error();
+                }
 
-    match format_result {
-        Ok((file_map, has_diff, has_macro_rewrite_failure)) => {
-            if report.has_warnings() {
-                summary.add_formatting_error();
-            }
+                if has_diff {
+                    self.summary.add_diff();
+                }
 
-            if has_diff {
-                summary.add_diff();
-            }
+                if has_macro_rewrite_failure {
+                    self.summary.add_macro_foramt_failure();
+                }
 
-            if has_macro_rewrite_failure {
-                summary.add_macro_foramt_failure();
+                Ok((file_map, report))
             }
-
-            Ok((summary, file_map, report))
+            Err(e) => Err(From::from(e)),
         }
-        Err(e) => Err((From::from(e), summary)),
     }
 }
 
@@ -551,43 +553,3 @@ pub(crate) struct ModifiedLines {
     /// The set of changed chunks.
     pub chunks: Vec<ModifiedChunk>,
 }
-
-/// Format a file and return a `ModifiedLines` data structure describing
-/// the changed ranges of lines.
-#[cfg(test)]
-pub(crate) fn get_modified_lines(
-    input: Input,
-    config: &Config,
-) -> Result<ModifiedLines, (ErrorKind, Summary)> {
-    use std::io::BufRead;
-
-    let mut data = Vec::new();
-
-    let mut config = config.clone();
-    config.set().emit_mode(::config::EmitMode::ModifiedLines);
-    ::format_input(input, &config, Some(&mut data))?;
-
-    let mut lines = data.lines();
-    let mut chunks = Vec::new();
-    while let Some(Ok(header)) = lines.next() {
-        // Parse the header line
-        let values: Vec<_> = header
-            .split(' ')
-            .map(|s| s.parse::<u32>().unwrap())
-            .collect();
-        assert_eq!(values.len(), 3);
-        let line_number_orig = values[0];
-        let lines_removed = values[1];
-        let num_added = values[2];
-        let mut added_lines = Vec::new();
-        for _ in 0..num_added {
-            added_lines.push(lines.next().unwrap().unwrap());
-        }
-        chunks.push(ModifiedChunk {
-            line_number_orig,
-            lines_removed,
-            lines: added_lines,
-        });
-    }
-    Ok(ModifiedLines { chunks })
-}
diff --git a/src/git-rustfmt/main.rs b/src/git-rustfmt/main.rs
index 545756576c9..d6ab3661482 100644
--- a/src/git-rustfmt/main.rs
+++ b/src/git-rustfmt/main.rs
@@ -22,7 +22,7 @@ use std::str::FromStr;
 
 use getopts::{Matches, Options};
 
-use rustfmt::{format_input, load_config, CliOptions, Input};
+use rustfmt::{load_config, CliOptions, Input, Session};
 
 fn prune_files(files: Vec<&str>) -> Vec<&str> {
     let prefixes: Vec<_> = files
@@ -73,16 +73,14 @@ fn fmt_files(files: &[&str]) -> i32 {
         load_config::<NullOptions>(Some(Path::new(".")), None).expect("couldn't load config");
 
     let mut exit_code = 0;
+    let mut out = stdout();
+    let mut session = Session::new(config, Some(&mut out));
     for file in files {
-        let (summary, report) = format_input(
-            Input::File(PathBuf::from(file)),
-            &config,
-            Some(&mut stdout()),
-        ).unwrap();
+        let report = session.format(Input::File(PathBuf::from(file))).unwrap();
         if report.has_warnings() {
             eprintln!("{}", report);
         }
-        if !summary.has_no_errors() {
+        if !session.summary.has_no_errors() {
             exit_code = 1;
         }
     }
diff --git a/src/imports.rs b/src/imports.rs
index 79d0641504e..f087ea5e52a 100644
--- a/src/imports.rs
+++ b/src/imports.rs
@@ -349,7 +349,7 @@ impl UseTree {
                 ));
             }
             UseTreeKind::Simple(ref rename, ..) => {
-                let mut name = rewrite_ident(context, path_to_imported_ident(&a.prefix)).to_owned();
+                let name = rewrite_ident(context, path_to_imported_ident(&a.prefix)).to_owned();
                 let alias = rename.and_then(|ident| {
                     if ident == path_to_imported_ident(&a.prefix) {
                         None
@@ -511,7 +511,7 @@ impl UseTree {
                 let prefix = &self.path[..self.path.len() - 1];
                 let mut result = vec![];
                 for nested_use_tree in list {
-                    for mut flattend in &mut nested_use_tree.clone().flatten() {
+                    for flattend in &mut nested_use_tree.clone().flatten() {
                         let mut new_path = prefix.to_vec();
                         new_path.append(&mut flattend.path);
                         result.push(UseTree {
@@ -532,7 +532,7 @@ impl UseTree {
 
     fn merge(&mut self, other: UseTree) {
         let mut new_path = vec![];
-        for (mut a, b) in self
+        for (a, b) in self
             .path
             .clone()
             .iter_mut()
diff --git a/src/items.rs b/src/items.rs
index 00db19f3b6c..721f02b0187 100644
--- a/src/items.rs
+++ b/src/items.rs
@@ -663,7 +663,7 @@ pub fn format_impl(
             option.compress_where();
         }
 
-        let mut where_clause_str = rewrite_where_clause(
+        let where_clause_str = rewrite_where_clause(
             context,
             &generics.where_clause,
             context.config.brace_style(),
diff --git a/src/lib.rs b/src/lib.rs
index 2c1ef49f354..6183117a58c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -14,6 +14,7 @@
 #![feature(type_ascription)]
 #![feature(unicode_internals)]
 #![feature(extern_prelude)]
+#![feature(nll)]
 
 #[macro_use]
 extern crate derive_new;
@@ -41,17 +42,17 @@ use std::cell::RefCell;
 use std::collections::HashMap;
 use std::fmt;
 use std::io::{self, Write};
+use std::mem;
 use std::path::PathBuf;
 use std::rc::Rc;
 use syntax::ast;
 
 use comment::LineClasses;
 use failure::Fail;
-use formatting::{format_input_inner, FormatErrorMap, FormattingError, ReportedErrors};
+use formatting::{FormatErrorMap, FormattingError, ReportedErrors};
 use issues::Issue;
 use shape::Indent;
 
-pub use checkstyle::{footer as checkstyle_footer, header as checkstyle_header};
 pub use config::summary::Summary;
 pub use config::{
     load_config, CliOptions, Color, Config, EmitMode, FileLines, FileName, NewlineStyle, Range,
@@ -348,13 +349,16 @@ fn format_snippet(snippet: &str, config: &Config) -> Option<String> {
     config.set().emit_mode(config::EmitMode::Stdout);
     config.set().verbose(Verbosity::Quiet);
     config.set().hide_parse_errors(true);
-    match format_input(input, &config, Some(&mut out)) {
-        // `format_input()` returns an empty string on parsing error.
-        Ok((summary, _)) if summary.has_macro_formatting_failure() => None,
-        Ok(..) if out.is_empty() && !snippet.is_empty() => None,
-        Ok(..) => String::from_utf8(out).ok(),
-        Err(..) => None,
+    {
+        let mut session = Session::new(config, Some(&mut out));
+        let result = session.format(input);
+        let formatting_error = session.summary.has_macro_formatting_failure()
+            || session.out.as_ref().unwrap().is_empty() && !snippet.is_empty();
+        if formatting_error || result.is_err() {
+            return None;
+        }
     }
+    String::from_utf8(out).ok()
 }
 
 /// Format the given code block. Mainly targeted for code block in comment.
@@ -436,24 +440,59 @@ fn format_code_block(code_snippet: &str, config: &Config) -> Option<String> {
     Some(result)
 }
 
-#[derive(Debug)]
-pub enum Input {
-    File(PathBuf),
-    Text(String),
+/// A session is a run of rustfmt across a single or multiple inputs.
+pub struct Session<'b, T: Write + 'b> {
+    pub config: Config,
+    pub out: Option<&'b mut T>,
+    pub summary: Summary,
+}
+
+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());
+        }
+
+        Session {
+            config,
+            out,
+            summary: Summary::default(),
+        }
+    }
+
+    /// 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> {
+        if !self.config.version_meets_requirement() {
+            return Err(ErrorKind::VersionMismatch);
+        }
+
+        syntax::with_globals(|| self.format_input_inner(input)).map(|tup| tup.1)
+    }
+
+    pub fn override_config<F, U>(&mut self, mut config: Config, f: F) -> U
+    where
+        F: FnOnce(&mut Session<'b, T>) -> U,
+    {
+        mem::swap(&mut config, &mut self.config);
+        let result = f(self);
+        mem::swap(&mut config, &mut self.config);
+        result
+    }
 }
 
-/// 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_input<T: Write>(
-    input: Input,
-    config: &Config,
-    out: Option<&mut T>,
-) -> Result<(Summary, FormatReport), (ErrorKind, Summary)> {
-    if !config.version_meets_requirement() {
-        return Err((ErrorKind::VersionMismatch, Summary::default()));
+impl<'b, T: Write + 'b> Drop for Session<'b, T> {
+    fn drop(&mut self) {
+        if self.config.emit_mode() == EmitMode::Checkstyle {
+            println!("{}", checkstyle::footer());
+        }
     }
+}
 
-    syntax::with_globals(|| format_input_inner(input, config, out)).map(|tup| (tup.0, tup.2))
+#[derive(Debug)]
+pub enum Input {
+    File(PathBuf),
+    Text(String),
 }
 
 #[cfg(test)]
diff --git a/src/test/mod.rs b/src/test/mod.rs
index b2eead3183d..f02cfbc858c 100644
--- a/src/test/mod.rs
+++ b/src/test/mod.rs
@@ -23,9 +23,9 @@ use std::str::Chars;
 use config::summary::Summary;
 use config::{Color, Config, EmitMode, FileName, ReportTactic};
 use filemap;
-use formatting::{format_input_inner, get_modified_lines, FileMap, ModifiedChunk, ModifiedLines};
+use formatting::{FileMap, ModifiedChunk};
 use rustfmt_diff::{make_diff, print_diff, DiffLine, Mismatch, OutputWriter};
-use {format_input, FormatReport, Input};
+use {FormatReport, Input, Session};
 
 const DIFF_CONTEXT_SIZE: usize = 3;
 const CONFIGURATIONS_FILE_NAME: &str = "Configurations.md";
@@ -142,25 +142,56 @@ fn checkstyle_test() {
 
 #[test]
 fn modified_test() {
+    use std::io::BufRead;
+
     // Test "modified" output
     let filename = "tests/writemode/source/modified.rs";
-    let result = get_modified_lines(Input::File(filename.into()), &Config::default()).unwrap();
-    assert_eq!(
-        result,
-        ModifiedLines {
-            chunks: vec![
-                ModifiedChunk {
-                    line_number_orig: 4,
-                    lines_removed: 4,
-                    lines: vec!["fn blah() {}".into()],
-                },
-                ModifiedChunk {
-                    line_number_orig: 9,
-                    lines_removed: 6,
-                    lines: vec!["#[cfg(a, b)]".into(), "fn main() {}".into()],
-                },
-            ],
+    let mut data = Vec::new();
+    let mut config = Config::default();
+    config.set().emit_mode(::config::EmitMode::ModifiedLines);
+
+    {
+        let mut session = Session::new(config, Some(&mut data));
+        session.format(Input::File(filename.into())).unwrap();
+    }
+
+    let mut lines = data.lines();
+    let mut chunks = Vec::new();
+    while let Some(Ok(header)) = lines.next() {
+        // Parse the header line
+        let values: Vec<_> = header
+            .split(' ')
+            .map(|s| s.parse::<u32>().unwrap())
+            .collect();
+        assert_eq!(values.len(), 3);
+        let line_number_orig = values[0];
+        let lines_removed = values[1];
+        let num_added = values[2];
+        let mut added_lines = Vec::new();
+        for _ in 0..num_added {
+            added_lines.push(lines.next().unwrap().unwrap());
         }
+        chunks.push(ModifiedChunk {
+            line_number_orig,
+            lines_removed,
+            lines: added_lines,
+        });
+    }
+
+    assert_eq!(
+        chunks,
+        vec![
+            ModifiedChunk {
+                line_number_orig: 4,
+                lines_removed: 4,
+                lines: vec!["fn blah() {}".into()],
+            },
+            ModifiedChunk {
+                line_number_orig: 9,
+                lines_removed: 6,
+                lines: vec!["#[cfg(a, b)]".into(), "fn main() {}".into()],
+            },
+        ],
     );
 }
 
@@ -168,7 +199,7 @@ fn modified_test() {
 // to a known output file generated by one of the write modes.
 fn assert_output(source: &Path, expected_filename: &Path) {
     let config = read_config(source);
-    let (_error_summary, file_map, _report) = format_file(source, &config);
+    let (_, file_map, _) = format_file(source, config.clone());
 
     // Populate output by writing to a vec.
     let mut out = vec![];
@@ -246,8 +277,11 @@ fn stdin_formatting_smoke_test() {
     let mut config = Config::default();
     config.set().emit_mode(EmitMode::Stdout);
     let mut buf: Vec<u8> = vec![];
-    let (error_summary, _) = format_input(input, &config, Some(&mut buf)).unwrap();
-    assert!(error_summary.has_no_errors());
+    {
+        let mut session = Session::new(config, Some(&mut buf));
+        session.format(input).unwrap();
+        assert!(session.summary.has_no_errors());
+    }
     //eprintln!("{:?}", );
     #[cfg(not(windows))]
     assert_eq!(buf, "fn main() {}\n".as_bytes());
@@ -284,8 +318,9 @@ fn format_lines_errors_are_reported() {
     let input = Input::Text(format!("fn {}() {{}}", long_identifier));
     let mut config = Config::default();
     config.set().error_on_line_overflow(true);
-    let (error_summary, _) = format_input::<io::Stdout>(input, &config, None).unwrap();
-    assert!(error_summary.has_formatting_errors());
+    let mut session = Session::<io::Stdout>::new(config, None);
+    session.format(input).unwrap();
+    assert!(session.summary.has_formatting_errors());
 }
 
 #[test]
@@ -295,8 +330,9 @@ fn format_lines_errors_are_reported_with_tabs() {
     let mut config = Config::default();
     config.set().error_on_line_overflow(true);
     config.set().hard_tabs(true);
-    let (error_summary, _) = format_input::<io::Stdout>(input, &config, None).unwrap();
-    assert!(error_summary.has_formatting_errors());
+    let mut session = Session::<io::Stdout>::new(config, None);
+    session.format(input).unwrap();
+    assert!(session.summary.has_formatting_errors());
 }
 
 // For each file, run rustfmt and collect the output.
@@ -380,11 +416,15 @@ fn read_config(filename: &Path) -> Config {
     config
 }
 
-fn format_file<P: Into<PathBuf>>(filepath: P, config: &Config) -> (Summary, FileMap, FormatReport) {
+fn format_file<P: Into<PathBuf>>(filepath: P, config: Config) -> (bool, FileMap, FormatReport) {
     let filepath = filepath.into();
     let input = Input::File(filepath);
-    //format_input::<io::Stdout>(input, config, None).unwrap()
-    syntax::with_globals(|| format_input_inner::<io::Stdout>(input, config, None)).unwrap()
+    let mut session = Session::<io::Stdout>::new(config, None);
+    syntax::with_globals(|| {
+        let result = session.format_input_inner(input).unwrap();
+        let parsing_errors = session.summary.has_parsing_errors();
+        (parsing_errors, result.0, result.1)
+    })
 }
 
 enum IdempotentCheckError {
@@ -402,8 +442,8 @@ fn idempotent_check(
     } else {
         read_config(filename)
     };
-    let (error_summary, file_map, format_report) = format_file(filename, &config);
-    if error_summary.has_parsing_errors() {
+    let (parsing_errors, file_map, format_report) = format_file(filename, config);
+    if parsing_errors {
         return Err(IdempotentCheckError::Parse);
     }
 
@@ -779,10 +819,15 @@ impl ConfigCodeBlock {
         config.set().emit_mode(EmitMode::Stdout);
         let mut buf: Vec<u8> = vec![];
 
-        let (error_summary, _) = format_input(input, &config, Some(&mut buf)).unwrap();
+        {
+            let mut session = Session::new(config, Some(&mut buf));
+            session.format(input).unwrap();
+            if self.has_parsing_errors(session.summary) {
+                return false;
+            }
+        }
 
-        !self.has_parsing_errors(error_summary)
-            && !self.formatted_has_diff(&String::from_utf8(buf).unwrap())
+        !self.formatted_has_diff(&String::from_utf8(buf).unwrap())
     }
 
     // Extract a code block from the iterator. Behavior: