// except according to those terms.
#![feature(rustc_private)]
-#![feature(collections)]
+#![feature(str_escape)]
#![feature(str_char)]
+#![feature(slice_extras)]
// TODO we're going to allocate a whole bunch of temp Strings, is it worth
// keeping some scratch mem for this and running our own StrPool?
use rustc::session::config::Input;
use rustc_driver::{driver, CompilerCalls, Compilation};
-use rustc_serialize::{Decodable, Decoder};
-
use syntax::ast;
use syntax::codemap::CodeMap;
use syntax::diagnostics;
use std::path::PathBuf;
use std::collections::HashMap;
+use std::fmt;
+use std::mem::swap;
+use issues::{BadIssueSeeker, Issue};
use changes::ChangeSet;
use visitor::FmtVisitor;
+use config::Config;
#[macro_use]
mod config;
+#[macro_use]
+mod utils;
mod changes;
mod visitor;
mod items;
mod missed_spans;
mod lists;
-mod utils;
mod types;
mod expr;
mod imports;
+mod issues;
+mod rewrite;
+mod string;
+mod comment;
const MIN_STRING: usize = 10;
// When we get scoped annotations, we should have rustfmt::skip.
const SKIP_ANNOTATION: &'static str = "rustfmt_skip";
-static mut CONFIG: Option<config::Config> = None;
-
-// Macro for deriving implementations of Decodable for enums
-macro_rules! impl_enum_decodable {
- ( $e:ident, $( $x:ident ),* ) => {
- impl Decodable for $e {
- fn decode<D: Decoder>(d: &mut D) -> Result<Self, D::Error> {
- let s = try!(d.read_str());
- match &*s {
- $(
- stringify!($x) => Ok($e::$x),
- )*
- _ => Err(d.error("Bad variant")),
- }
- }
- }
- };
-}
-
#[derive(Copy, Clone)]
pub enum WriteMode {
Overwrite,
impl_enum_decodable!(ReturnIndent, WithArgs, WithWhereClause);
-// Formatting which depends on the AST.
-fn fmt_ast<'a>(krate: &ast::Crate, codemap: &'a CodeMap) -> ChangeSet<'a> {
- let mut visitor = FmtVisitor::from_codemap(codemap);
- visit::walk_crate(&mut visitor, krate);
- let files = codemap.files.borrow();
- if let Some(last) = files.last() {
- visitor.format_missing(last.end_pos);
+enum ErrorKind {
+ // Line has exceeded character limit
+ LineOverflow,
+ // Line ends in whitespace
+ TrailingWhitespace,
+ // TO-DO or FIX-ME item without an issue number
+ BadIssue(Issue),
+}
+
+impl fmt::Display for ErrorKind {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ match *self {
+ ErrorKind::LineOverflow => {
+ write!(fmt, "line exceeded maximum length")
+ },
+ ErrorKind::TrailingWhitespace => {
+ write!(fmt, "left behind trailing whitespace")
+ },
+ ErrorKind::BadIssue(issue) => {
+ write!(fmt, "found {}", issue)
+ },
+ }
}
+}
+
+// Formatting errors that are identified *after* rustfmt has run
+struct FormattingError {
+ line: u32,
+ kind: ErrorKind,
+}
+struct FormatReport {
+ // Maps stringified file paths to their associated formatting errors
+ file_error_map: HashMap<String, Vec<FormattingError>>,
+}
+
+impl fmt::Display for FormatReport {
+ // Prints all the formatting errors.
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ for (file, errors) in self.file_error_map.iter() {
+ for error in errors {
+ try!(write!(fmt,
+ "Rustfmt failed at {}:{}: {} (sorry)\n",
+ file,
+ error.line,
+ error.kind));
+ }
+ }
+ Ok(())
+ }
+}
+
+// Formatting which depends on the AST.
+fn fmt_ast<'a>(krate: &ast::Crate, codemap: &'a CodeMap, config: &'a Config) -> ChangeSet<'a> {
+ let mut visitor = FmtVisitor::from_codemap(codemap, config);
+ visit::walk_crate(&mut visitor, krate);
visitor.changes
}
// Formatting done on a char by char or line by line basis.
-// TODO warn on TODOs and FIXMEs without an issue number
// TODO warn on bad license
// TODO other stuff for parity with make tidy
-fn fmt_lines(changes: &mut ChangeSet) {
+fn fmt_lines(changes: &mut ChangeSet, config: &Config) -> FormatReport {
let mut truncate_todo = Vec::new();
+ let mut report = FormatReport { file_error_map: HashMap::new() };
// Iterate over the chars in the change set.
for (f, text) in changes.text() {
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);
+
for (c, b) in text.chars() {
if c == '\r' { continue; }
+
+ // Add warnings for bad todos/ fixmes
+ if let Some(issue) = issue_seeker.inspect(c) {
+ errors.push(FormattingError {
+ line: cur_line,
+ kind: ErrorKind::BadIssue(issue)
+ });
+ }
+
if c == '\n' {
// Check for (and record) trailing whitespace.
if let Some(lw) = last_wspace {
line_len -= b - lw;
}
// Check for any line width errors we couldn't correct.
- if line_len > config!(max_width) {
- // TODO store the error rather than reporting immediately.
- println!("Rustfmt couldn't fix (sorry). {}:{}: line longer than {} characters",
- f, cur_line, config!(max_width));
+ if line_len > config.max_width {
+ errors.push(FormattingError {
+ line: cur_line,
+ kind: ErrorKind::LineOverflow
+ });
}
line_len = 0;
cur_line += 1;
if newline_count > 1 {
debug!("track truncate: {} {} {}", f, text.len, newline_count);
- truncate_todo.push((f.to_string(), text.len - newline_count + 1))
+ truncate_todo.push((f.to_owned(), text.len - newline_count + 1))
}
for &(l, _, _) in trims.iter() {
- // TODO store the error rather than reporting immediately.
- println!("Rustfmt left trailing whitespace at {}:{} (sorry)", f, l);
+ errors.push(FormattingError {
+ line: l,
+ kind: ErrorKind::TrailingWhitespace
+ });
}
+
+ report.file_error_map.insert(f.to_owned(), errors);
}
for (f, l) in truncate_todo {
changes.get_mut(&f).truncate(l);
}
+
+ report
}
struct RustFmtCalls {
input_path: Option<PathBuf>,
write_mode: WriteMode,
+ config: Option<Box<config::Config>>,
}
impl<'a> CompilerCalls<'a> for RustFmtCalls {
fn build_controller(&mut self, _: &Session) -> driver::CompileController<'a> {
let write_mode = self.write_mode;
+
+ let mut config_option = None;
+ swap(&mut self.config, &mut config_option);
+ let config = config_option.unwrap();
+
let mut control = driver::CompileController::basic();
control.after_parse.stop = Compilation::Stop;
control.after_parse.callback = Box::new(move |state| {
let krate = state.krate.unwrap();
let codemap = state.session.codemap();
- let mut changes = fmt_ast(krate, codemap);
+ let mut changes = fmt_ast(krate, codemap, &*config);
// For some reason, the codemap does not include terminating newlines
// so we must add one on for each file. This is sad.
changes.append_newlines();
- fmt_lines(&mut changes);
+ println!("{}", fmt_lines(&mut changes, &*config));
- let result = changes.write_all_files(write_mode);
+ let result = changes.write_all_files(write_mode, &*config);
match result {
Err(msg) => println!("Error writing files: {}", msg),
// WriteMode.
// default_config is a string of toml data to be used to configure rustfmt.
pub fn run(args: Vec<String>, write_mode: WriteMode, default_config: &str) {
- config::set_config(default_config);
-
- let mut call_ctxt = RustFmtCalls { input_path: None, write_mode: write_mode };
+ let config = Some(Box::new(config::Config::from_toml(default_config)));
+ let mut call_ctxt = RustFmtCalls { input_path: None, write_mode: write_mode, config: config };
rustc_driver::run_compiler(&args, &mut call_ctxt);
}