factor out a Session
object
This commit is contained in:
parent
4153e66e42
commit
71d3d04270
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -533,7 +533,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustfmt-nightly"
|
||||
version = "0.8.3"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"assert_cli 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cargo_metadata 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
|
||||
name = "rustfmt-nightly"
|
||||
version = "0.8.3"
|
||||
version = "0.9.0"
|
||||
authors = ["Nicholas Cameron <ncameron@mozilla.com>", "The Rustfmt developers"]
|
||||
description = "Tool to find and fix Rust formatting issues"
|
||||
repository = "https://github.com/rust-lang-nursery/rustfmt"
|
||||
|
163
src/bin/main.rs
163
src/bin/main.rs
@ -28,8 +28,8 @@
|
||||
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
|
||||
} 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;
|
||||
}
|
||||
session.override_config(local_config, |sess| {
|
||||
format_and_emit_report(sess, Input::File(file))
|
||||
});
|
||||
} else {
|
||||
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.
|
||||
|
@ -15,9 +15,8 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
return Ok((summary, FileMap::new(), FormatReport::new()));
|
||||
}
|
||||
let codemap = Rc::new(CodeMap::new(FilePathMapping::empty()));
|
||||
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());
|
||||
|
||||
let tty_handler = if config.hide_parse_errors() {
|
||||
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()));
|
||||
}
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
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());
|
||||
parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter);
|
||||
|
||||
let main_file = match input {
|
||||
Input::File(ref file) => FileName::Real(file.clone()),
|
||||
Input::Text(..) => FileName::Stdin,
|
||||
};
|
||||
let report = FormatReport::new();
|
||||
|
||||
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")
|
||||
});
|
||||
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)
|
||||
},
|
||||
);
|
||||
|
||||
summary.mark_parse_time();
|
||||
self.summary.mark_format_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);
|
||||
}
|
||||
Ok(false)
|
||||
},
|
||||
);
|
||||
|
||||
summary.mark_format_time();
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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() {
|
||||
summary.add_formatting_error();
|
||||
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
|
||||
}
|
||||
|
||||
if has_diff {
|
||||
summary.add_diff();
|
||||
}
|
||||
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()),
|
||||
)
|
||||
});
|
||||
|
||||
if has_macro_rewrite_failure {
|
||||
summary.add_macro_foramt_failure();
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
Ok((summary, file_map, report))
|
||||
}
|
||||
Err(e) => Err((From::from(e), summary)),
|
||||
|
||||
match format_result {
|
||||
Ok((file_map, has_diff, has_macro_rewrite_failure)) => {
|
||||
if report.has_warnings() {
|
||||
self.summary.add_formatting_error();
|
||||
}
|
||||
|
||||
if has_diff {
|
||||
self.summary.add_diff();
|
||||
}
|
||||
|
||||
if has_macro_rewrite_failure {
|
||||
self.summary.add_macro_foramt_failure();
|
||||
}
|
||||
|
||||
Ok((file_map, report))
|
||||
}
|
||||
Err(e) => Err(From::from(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 })
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -349,7 +349,7 @@ fn from_ast(
|
||||
));
|
||||
}
|
||||
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 @@ fn flatten(self) -> Vec<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 @@ fn flatten(self) -> Vec<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()
|
||||
|
@ -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(),
|
||||
|
83
src/lib.rs
83
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::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,26 +440,61 @@ fn enclose_in_main_block(s: &str, config: &Config) -> String {
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b, T: Write + 'b> Drop for Session<'b, T> {
|
||||
fn drop(&mut self) {
|
||||
if self.config.emit_mode() == EmitMode::Checkstyle {
|
||||
println!("{}", checkstyle::footer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Input {
|
||||
File(PathBuf),
|
||||
Text(String),
|
||||
}
|
||||
|
||||
/// 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()));
|
||||
}
|
||||
|
||||
syntax::with_globals(|| format_input_inner(input, config, out)).map(|tup| (tup.0, tup.2))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests {
|
||||
use super::*;
|
||||
|
111
src/test/mod.rs
111
src/test/mod.rs
@ -23,9 +23,9 @@
|
||||
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 @@ fn formatted_is_idempotent(&self) -> bool {
|
||||
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:
|
||||
|
Loading…
Reference in New Issue
Block a user