]> git.lizzy.rs Git - rust.git/blobdiff - src/lib.rs
Merge pull request #128 from marcusklaas/subexpr
[rust.git] / src / lib.rs
index 868f0ca663b944198f7897fe8d32667bf4c94ed3..252b3ad05b71a1ea6eebd75915665a15b823a866 100644 (file)
@@ -9,8 +9,9 @@
 // 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?
@@ -33,8 +34,6 @@
 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,
@@ -122,24 +111,71 @@ pub enum ReturnIndent {
 
 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() {
@@ -148,8 +184,21 @@ fn fmt_lines(changes: &mut ChangeSet) {
         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 {
@@ -157,10 +206,11 @@ fn fmt_lines(changes: &mut ChangeSet) {
                     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;
@@ -181,23 +231,30 @@ fn fmt_lines(changes: &mut ChangeSet) {
 
         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 {
@@ -244,18 +301,23 @@ fn late_callback(&mut self,
 
     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),
@@ -277,8 +339,7 @@ fn build_controller(&mut self, _: &Session) -> driver::CompileController<'a> {
 // 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);
 }