Skip to content

Commit 77350e4

Browse files
committed
return non-zero exit code if there were errors
1 parent 3cc24c6 commit 77350e4

File tree

5 files changed

+132
-21
lines changed

5 files changed

+132
-21
lines changed

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,18 @@ diff, replace, overwrite, display, coverage, and checkstyle.
6363
The write mode can be set by passing the `--write-mode` flag on
6464
the command line. For example `rustfmt --write-mode=display src/filename.rs`
6565

66-
You can run `rustfmt --help` for more information.
67-
6866
`cargo fmt` uses `--write-mode=replace` by default.
6967

68+
If `rustfmt` successfully reformatted the code it will exit with `0` exit
69+
status. Exit status `1` signals some unexpected error, like an unknown option or
70+
a failure to read a file. Exit status `2` is returned if there are syntax errors
71+
in the input files. `rustfmt` can't format syntatically invalid code. Finally,
72+
exit status `3` is returned if there are some issues which can't be resolved
73+
automatically. For example, if you have a very long comment line `rustfmt`
74+
doesn't split it. Instead it prints a warning and exits with `3`.
75+
76+
You can run `rustfmt --help` for more information.
77+
7078

7179
## Running Rustfmt from your editor
7280

src/bin/rustfmt.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ extern crate toml;
1717
extern crate env_logger;
1818
extern crate getopts;
1919

20-
use rustfmt::{run, Input};
20+
use rustfmt::{run, Input, Summary};
2121
use rustfmt::config::{Config, WriteMode};
2222

2323
use std::{env, error};
@@ -156,18 +156,21 @@ fn make_opts() -> Options {
156156
opts
157157
}
158158

159-
fn execute(opts: &Options) -> FmtResult<()> {
159+
fn execute(opts: &Options) -> FmtResult<Summary> {
160160
let matches = try!(opts.parse(env::args().skip(1)));
161161

162162
match try!(determine_operation(&matches)) {
163163
Operation::Help => {
164164
print_usage(&opts, "");
165+
Ok(Summary::new())
165166
}
166167
Operation::Version => {
167168
print_version();
169+
Ok(Summary::new())
168170
}
169171
Operation::ConfigHelp => {
170172
Config::print_docs();
173+
Ok(Summary::new())
171174
}
172175
Operation::Stdin { input, config_path } => {
173176
// try to read config from local directory
@@ -177,7 +180,7 @@ fn execute(opts: &Options) -> FmtResult<()> {
177180
// write_mode is always Plain for Stdin.
178181
config.write_mode = WriteMode::Plain;
179182

180-
run(Input::Text(input), &config);
183+
Ok(run(Input::Text(input), &config))
181184
}
182185
Operation::Format { files, config_path } => {
183186
let mut config = Config::default();
@@ -193,6 +196,8 @@ fn execute(opts: &Options) -> FmtResult<()> {
193196
if let Some(path) = path.as_ref() {
194197
println!("Using rustfmt config file {}", path.display());
195198
}
199+
200+
let mut error_summary = Summary::new();
196201
for file in files {
197202
// Check the file directory if the config-path could not be read or not provided
198203
if path.is_none() {
@@ -209,11 +214,11 @@ fn execute(opts: &Options) -> FmtResult<()> {
209214
}
210215

211216
try!(update_config(&mut config, &matches));
212-
run(Input::File(file), &config);
217+
error_summary.add(run(Input::File(file), &config));
213218
}
219+
Ok(error_summary)
214220
}
215221
}
216-
Ok(())
217222
}
218223

219224
fn main() {
@@ -222,7 +227,18 @@ fn main() {
222227
let opts = make_opts();
223228

224229
let exit_code = match execute(&opts) {
225-
Ok(..) => 0,
230+
Ok(summary) => {
231+
if summary.has_operational_errors() {
232+
1
233+
} else if summary.has_parsing_errors() {
234+
2
235+
} else if summary.has_formatting_errors() {
236+
3
237+
} else {
238+
assert!(summary.has_no_errors());
239+
0
240+
}
241+
}
226242
Err(e) => {
227243
print_usage(&opts, &e.to_string());
228244
1

src/lib.rs

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ use std::fmt;
4141
use issues::{BadIssueSeeker, Issue};
4242
use filemap::FileMap;
4343
use visitor::FmtVisitor;
44-
use config::{Config, WriteMode};
44+
use config::Config;
45+
46+
pub use self::summary::Summary;
4547

4648
#[macro_use]
4749
mod utils;
@@ -64,6 +66,7 @@ pub mod rustfmt_diff;
6466
mod chains;
6567
mod macros;
6668
mod patterns;
69+
mod summary;
6770

6871
const MIN_STRING: usize = 10;
6972
// When we get scoped annotations, we should have rustfmt::skip.
@@ -239,9 +242,17 @@ pub struct FormatReport {
239242
}
240243

241244
impl FormatReport {
245+
fn new() -> FormatReport {
246+
FormatReport { file_error_map: HashMap::new() }
247+
}
248+
242249
pub fn warning_count(&self) -> usize {
243250
self.file_error_map.iter().map(|(_, ref errors)| errors.len()).fold(0, |acc, x| acc + x)
244251
}
252+
253+
pub fn has_warnings(&self) -> bool {
254+
self.warning_count() > 0
255+
}
245256
}
246257

247258
impl fmt::Display for FormatReport {
@@ -289,7 +300,7 @@ fn format_ast(krate: &ast::Crate,
289300
// TODO(#20) other stuff for parity with make tidy
290301
fn format_lines(file_map: &mut FileMap, config: &Config) -> FormatReport {
291302
let mut truncate_todo = Vec::new();
292-
let mut report = FormatReport { file_error_map: HashMap::new() };
303+
let mut report = FormatReport::new();
293304

294305
// Iterate over the chars in the file map.
295306
for (f, text) in file_map.iter() {
@@ -368,17 +379,16 @@ fn format_lines(file_map: &mut FileMap, config: &Config) -> FormatReport {
368379
}
369380

370381
fn parse_input(input: Input, parse_session: &ParseSess) -> Result<ast::Crate, DiagnosticBuilder> {
371-
let krate = match input {
382+
match input {
372383
Input::File(file) => parse::parse_crate_from_file(&file, Vec::new(), &parse_session),
373384
Input::Text(text) => {
374385
parse::parse_crate_from_source_str("stdin".to_owned(), text, Vec::new(), &parse_session)
375386
}
376-
};
377-
378-
krate
387+
}
379388
}
380389

381-
pub fn format_input(input: Input, config: &Config) -> (FileMap, FormatReport) {
390+
pub fn format_input(input: Input, config: &Config) -> (Summary, FileMap, FormatReport) {
391+
let mut summary = Summary::new();
382392
let codemap = Rc::new(CodeMap::new());
383393

384394
let tty_handler = Handler::with_tty_emitter(ColorConfig::Auto,
@@ -397,10 +407,15 @@ pub fn format_input(input: Input, config: &Config) -> (FileMap, FormatReport) {
397407
Ok(krate) => krate,
398408
Err(mut diagnostic) => {
399409
diagnostic.emit();
400-
panic!("Unrecoverable parse error");
410+
summary.add_parsing_error();
411+
return (summary, FileMap::new(), FormatReport::new());
401412
}
402413
};
403414

415+
if parse_session.span_diagnostic.has_errors() {
416+
summary.add_parsing_error();
417+
}
418+
404419
// Suppress error output after parsing.
405420
let silent_emitter = Box::new(EmitterWriter::new(Box::new(Vec::new()), None, codemap.clone()));
406421
parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter);
@@ -412,22 +427,28 @@ pub fn format_input(input: Input, config: &Config) -> (FileMap, FormatReport) {
412427
filemap::append_newlines(&mut file_map);
413428

414429
let report = format_lines(&mut file_map, config);
415-
(file_map, report)
430+
if report.has_warnings() {
431+
summary.add_formatting_error();
432+
}
433+
(summary, file_map, report)
416434
}
417435

418436
pub enum Input {
419437
File(PathBuf),
420438
Text(String),
421439
}
422440

423-
pub fn run(input: Input, config: &Config) {
424-
let (file_map, report) = format_input(input, config);
441+
pub fn run(input: Input, config: &Config) -> Summary {
442+
let (mut summary, file_map, report) = format_input(input, config);
425443
msg!("{}", report);
426444

427445
let mut out = stdout();
428446
let write_result = filemap::write_all_files(&file_map, &mut out, config);
429447

430448
if let Err(msg) = write_result {
431449
msg!("Error writing files: {}", msg);
450+
summary.add_operational_error();
432451
}
452+
453+
summary
433454
}

src/summary.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#[must_use]
2+
pub struct Summary {
3+
// Encountered e.g. an IO error.
4+
has_operational_errors: bool,
5+
6+
// Failed to reformat code because of parsing errors.
7+
has_parsing_errors: bool,
8+
9+
// Code is valid, but it is impossible to format it properly.
10+
has_formatting_errors: bool,
11+
}
12+
13+
impl Summary {
14+
pub fn new() -> Summary {
15+
Summary {
16+
has_operational_errors: false,
17+
has_parsing_errors: false,
18+
has_formatting_errors: false,
19+
}
20+
}
21+
22+
pub fn has_operational_errors(&self) -> bool {
23+
self.has_operational_errors
24+
}
25+
26+
pub fn has_parsing_errors(&self) -> bool {
27+
self.has_parsing_errors
28+
}
29+
30+
pub fn has_formatting_errors(&self) -> bool {
31+
self.has_formatting_errors
32+
}
33+
34+
pub fn add_operational_error(&mut self) {
35+
self.has_operational_errors = true;
36+
}
37+
38+
pub fn add_parsing_error(&mut self) {
39+
self.has_parsing_errors = true;
40+
}
41+
42+
pub fn add_formatting_error(&mut self) {
43+
self.has_formatting_errors = true;
44+
}
45+
46+
pub fn has_no_errors(&self) -> bool {
47+
!(self.has_operational_errors || self.has_parsing_errors || self.has_formatting_errors)
48+
}
49+
50+
pub fn add(&mut self, other: Summary) {
51+
self.has_operational_errors |= other.has_operational_errors;
52+
self.has_formatting_errors |= other.has_formatting_errors;
53+
self.has_parsing_errors |= other.has_parsing_errors;
54+
}
55+
}

tests/system.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,20 @@ fn self_tests() {
143143
fn stdin_formatting_smoke_test() {
144144
let input = Input::Text("fn main () {}".to_owned());
145145
let config = Config::default();
146-
let (file_map, _report) = format_input(input, &config);
146+
let (error_summary, file_map, _report) = format_input(input, &config);
147+
assert!(error_summary.has_no_errors());
147148
assert_eq!(file_map["stdin"].to_string(), "fn main() {}\n")
148149
}
149150

151+
#[test]
152+
fn format_lines_errors_are_reported() {
153+
let long_identifier = String::from_utf8(vec![b'a'; 239]).unwrap();
154+
let input = Input::Text(format!("fn {}() {{}}", long_identifier));
155+
let config = Config::default();
156+
let (error_summary, _file_map, _report) = format_input(input, &config);
157+
assert!(error_summary.has_formatting_errors());
158+
}
159+
150160
// For each file, run rustfmt and collect the output.
151161
// Returns the number of files checked and the number of failures.
152162
fn check_files<I>(files: I) -> (Vec<FormatReport>, u32, u32)
@@ -202,7 +212,8 @@ fn read_config(filename: &str) -> Config {
202212

203213
fn format_file<P: Into<PathBuf>>(filename: P, config: &Config) -> (FileMap, FormatReport) {
204214
let input = Input::File(filename.into());
205-
format_input(input, &config)
215+
let (_error_summary, file_map, report) = format_input(input, &config);
216+
return (file_map, report);
206217
}
207218

208219
pub fn idempotent_check(filename: String) -> Result<FormatReport, HashMap<String, Vec<Mismatch>>> {

0 commit comments

Comments
 (0)