diff --git a/.travis.yml b/.travis.yml index 9683ca582eb..8025d275cc9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: rust rust: + - stable + - beta - nightly sudo: false diff --git a/Cargo.lock b/Cargo.lock index c00a673ebf6..66061bd1e25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,9 +3,13 @@ name = "rustfmt" version = "0.0.1" dependencies = [ "diff 0.1.7 (git+https://github.com/utkarshkukreti/diff.rs.git)", + "env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", "strings 0.0.1 (git+https://github.com/nrc/strings.rs.git)", + "syntex_syntax 0.18.0 (git+https://github.com/serde-rs/syntex)", "term 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-segmentation 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -19,11 +23,30 @@ dependencies = [ "memchr 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "bitflags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "diff" version = "0.1.7" source = "git+https://github.com/utkarshkukreti/diff.rs.git#6edb9454bf4127087aced0fe07ab3ea6894083cb" +[[package]] +name = "env_logger" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "getopts" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "kernel32-sys" version = "0.1.4" @@ -82,6 +105,19 @@ dependencies = [ "log 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "syntex_syntax" +version = "0.18.0" +source = "git+https://github.com/serde-rs/syntex#176ca5d8add606fac8d503b10c89ddb82f02d92b" +dependencies = [ + "bitflags 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", + "term 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "term" version = "0.2.12" @@ -104,6 +140,11 @@ name = "unicode-segmentation" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-xid" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.2.4" diff --git a/Cargo.toml b/Cargo.toml index e8b81eb8482..ebd4e8e7f1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,5 +16,9 @@ regex = "0.1.41" term = "0.2.11" strings = { version = "0.0.1", git = "https://github.com/nrc/strings.rs.git" } diff = { git = "https://github.com/utkarshkukreti/diff.rs.git" } +syntex_syntax = { git = "https://github.com/serde-rs/syntex" } +log = "0.3.2" +env_logger = "0.3.1" +getopts = "0.2" [dev-dependencies] diff --git a/README.md b/README.md index 8c76e8ec51c..06e8dae54f3 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,34 @@ A tool for formatting Rust code according to style guidelines. ## Gotchas -* For things you do not want rustfmt to mangle, use -```rust - #[rustfmt_skip] - ``` -* When you run rustfmt use a file called rustfmt.toml to override the default settings of rustfmt. -* We create a functioning executable called rustfmt in the target directory + +* For things you do not want rustfmt to mangle, use one of + ```rust + #[rustfmt_skip] + #[cfg_attr(rustfmt, rustfmt_skip)] + ``` +* When you run rustfmt use a file called rustfmt.toml to override the default + settings of rustfmt. +* We create a functioning executable called `rustfmt` in the target directory + +## Installation + +> **Note:** this method currently requires you to be running a nightly install +> of Rust as `cargo install` has not yet made its way onto the stable channel. + +``` +cargo install --git https://github.com/nrc/rustfmt +``` + +or if you're using `multirust` + +``` +multirust run nightly cargo install --git https://github.com/nrc/rustfmt +``` ## How to build and test -You'll need a pretty up to date version of the **nightly** version of Rust. + +First make sure you've got Rust **1.3.0** or greater available, then: `cargo build` to build. diff --git a/src/bin/rustfmt.rs b/src/bin/rustfmt.rs index 01c292948df..6b3405aa936 100644 --- a/src/bin/rustfmt.rs +++ b/src/bin/rustfmt.rs @@ -7,30 +7,32 @@ // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. -#![feature(path_ext)] -#![feature(rustc_private)] + #![cfg(not(test))] #[macro_use] extern crate log; extern crate rustfmt; extern crate toml; +extern crate env_logger; +extern crate getopts; use rustfmt::{WriteMode, run}; use rustfmt::config::Config; use std::env; -use std::fs::{File, PathExt}; +use std::fs::{self, File}; use std::io::{self, Read}; use std::path::PathBuf; -use std::str::FromStr; + +use getopts::Options; // Try to find a project file in the current directory and its parents. fn lookup_project_file() -> io::Result { let mut current = try!(env::current_dir()); loop { let config_file = current.join("rustfmt.toml"); - if config_file.exists() { + if fs::metadata(&config_file).is_ok() { return Ok(config_file); } else { current = match current.parent() { @@ -52,7 +54,7 @@ fn lookup_and_read_project_file() -> io::Result<(PathBuf, String)> { } fn execute() -> i32 { - let (args, write_mode) = match determine_params(std::env::args()) { + let (file, write_mode) = match determine_params(std::env::args().skip(1)) { Some(params) => params, None => return 1, }; @@ -65,12 +67,14 @@ fn execute() -> i32 { Err(_) => Default::default(), }; - run(args, write_mode, &config); + run(&file, write_mode, &config); 0 } fn main() { use std::io::Write; + let _ = env_logger::init(); + let exit_code = execute(); // Make sure standard output is flushed before we exit std::io::stdout().flush().unwrap(); @@ -81,50 +85,52 @@ fn main() { std::process::exit(exit_code); } -fn print_usage>(reason: S) { - println!("{}\n\r usage: rustfmt [-h Help] [--write-mode=[replace|overwrite|display|diff]] \ - ", - reason.into()); +fn print_usage(opts: &Options, reason: &str) { + let reason = format!("{}\nusage: {} [options] ", + reason, + env::current_exe().unwrap().display()); + println!("{}", opts.usage(&reason)); Config::print_docs(); } -fn determine_params(args: I) -> Option<(Vec, WriteMode)> +fn determine_params(args: I) -> Option<(PathBuf, WriteMode)> where I: Iterator { - let arg_prefix = "-"; - let write_mode_prefix = "--write-mode="; - let help_mode = "-h"; - let long_help_mode = "--help"; - let mut write_mode = WriteMode::Replace; - let mut rustc_args = Vec::new(); + let mut opts = Options::new(); + opts.optflag("h", "help", "show this message"); + opts.optopt("", + "write-mode", + "mode to write in", + "[replace|overwrite|display|diff]"); + let matches = match opts.parse(args) { + Ok(m) => m, + Err(e) => { + print_usage(&opts, &e.to_string()); + return None; + } + }; - // The NewFile option currently isn't supported because it requires another - // parameter, but it can be added later. - for arg in args { - if arg.starts_with(write_mode_prefix) { - match FromStr::from_str(&arg[write_mode_prefix.len()..]) { - Ok(mode) => write_mode = mode, - Err(_) => { - print_usage("Unrecognized write mode"); + if matches.opt_present("h") { + print_usage(&opts, ""); + } + + let write_mode = match matches.opt_str("write-mode") { + Some(mode) => { + match mode.parse() { + Ok(mode) => mode, + Err(..) => { + print_usage(&opts, "Unrecognized write mode"); return None; } } - } else if arg.starts_with(help_mode) || arg.starts_with(long_help_mode) { - print_usage(""); - return None; - } else if arg.starts_with(arg_prefix) { - print_usage("Invalid argument"); - return None; - } else { - // Pass everything else to rustc - rustc_args.push(arg); } - } + None => WriteMode::Replace, + }; - if rustc_args.len() < 2 { - print_usage("Please provide a file to be formatted"); + if matches.free.len() != 1 { + print_usage(&opts, "Please provide one file to format"); return None; } - Some((rustc_args, write_mode)) + Some((PathBuf::from(&matches.free[0]), write_mode)) } diff --git a/src/chains.rs b/src/chains.rs index 4b6824a9073..4716f545b3f 100644 --- a/src/chains.rs +++ b/src/chains.rs @@ -72,11 +72,9 @@ pub fn rewrite_chain(mut expr: &ast::Expr, .collect::>>()); // Total of all items excluding the last. - let almost_total = rewrites.split_last() - .unwrap() - .1 - .iter() - .fold(0, |a, b| a + first_line_width(b)) + + let almost_total = rewrites[..rewrites.len() - 1] + .iter() + .fold(0, |a, b| a + first_line_width(b)) + parent_rewrite.len(); let total_width = almost_total + first_line_width(rewrites.last().unwrap()); let veto_single_line = if context.config.take_source_hints && subexpr_list.len() > 1 { @@ -95,7 +93,9 @@ pub fn rewrite_chain(mut expr: &ast::Expr, match subexpr_list[0].node { ast::Expr_::ExprMethodCall(ref method_name, ref types, ref expressions) if context.config.chains_overflow_last => { - let (last, init) = rewrites.split_last_mut().unwrap(); + let len = rewrites.len(); + let (init, last) = rewrites.split_at_mut(len - 1); + let last = &mut last[0]; if init.iter().all(|s| !s.contains('\n')) && total_width <= width { let last_rewrite = width.checked_sub(almost_total) diff --git a/src/comment.rs b/src/comment.rs index 045d1cb5047..091b4293ad3 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -437,7 +437,7 @@ mod test { } #[test] - #[rustfmt_skip] + #[cfg_attr(rustfmt, rustfmt_skip)] fn format_comments() { let config = Default::default(); assert_eq!("/* test */", rewrite_comment(" //test", true, 100, Indent::new(0, 100), diff --git a/src/lib.rs b/src/lib.rs index 03422a20c36..51497e3b1c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,13 +8,6 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![feature(rustc_private)] -#![feature(custom_attribute)] -#![feature(slice_splits)] -#![feature(slice_patterns)] -#![feature(catch_panic)] -#![allow(unused_attributes)] - // 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? // TODO for lint violations of names, emit a refactor script @@ -22,10 +15,7 @@ #[macro_use] extern crate log; -extern crate getopts; -extern crate rustc; -extern crate rustc_driver; -extern crate syntax; +extern crate syntex_syntax as syntax; extern crate rustc_serialize; extern crate strings; @@ -35,22 +25,15 @@ extern crate regex; extern crate diff; extern crate term; -use rustc::session::Session; -use rustc::session::config as rustc_config; -use rustc::session::config::Input; -use rustc_driver::{driver, CompilerCalls, Compilation}; - use syntax::ast; use syntax::codemap::{CodeMap, Span}; -use syntax::diagnostics; +use syntax::parse::{self, ParseSess}; use std::ops::{Add, Sub}; -use std::path::PathBuf; +use std::path::Path; use std::collections::HashMap; use std::fmt; use std::str::FromStr; -use std::rc::Rc; -use std::cell::RefCell; use issues::{BadIssueSeeker, Issue}; use filemap::FileMap; @@ -387,65 +370,24 @@ pub fn fmt_lines(file_map: &mut FileMap, config: &Config) -> FormatReport { report } -struct RustFmtCalls { - config: Rc, - result: Rc>>, -} +pub fn format(file: &Path, config: &Config) -> FileMap { + let parse_session = ParseSess::new(); + let krate = parse::parse_crate_from_file(file, Vec::new(), &parse_session); + let mut file_map = fmt_ast(&krate, parse_session.codemap(), config); -impl<'a> CompilerCalls<'a> for RustFmtCalls { - fn no_input(&mut self, - _: &getopts::Matches, - _: &rustc_config::Options, - _: &Option, - _: &Option, - _: &diagnostics::registry::Registry) - -> Option<(Input, Option)> { - panic!("No input supplied to RustFmt"); - } + // For some reason, the codemap does not include terminating + // newlines so we must add one on for each file. This is sad. + filemap::append_newlines(&mut file_map); - fn build_controller(&mut self, _: &Session) -> driver::CompileController<'a> { - let result = self.result.clone(); - let config = self.config.clone(); - - 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 file_map = 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. - filemap::append_newlines(&mut file_map); - - *result.borrow_mut() = Some(file_map); - }); - - control - } -} - -pub fn format(args: Vec, config: &Config) -> FileMap { - let result = Rc::new(RefCell::new(None)); - - { - let config = Rc::new(config.clone()); - let mut call_ctxt = RustFmtCalls { - config: config, - result: result.clone(), - }; - rustc_driver::run_compiler(&args, &mut call_ctxt); - } - - // Peel the union. - Rc::try_unwrap(result).ok().unwrap().into_inner().unwrap() + return file_map; } // args are the arguments passed on the command line, generally passed through // to the compiler. // write_mode determines what happens to the result of running rustfmt, see // WriteMode. -pub fn run(args: Vec, write_mode: WriteMode, config: &Config) { - let mut result = format(args, config); +pub fn run(file: &Path, write_mode: WriteMode, config: &Config) { + let mut result = format(file, config); println!("{}", fmt_lines(&mut result, config)); diff --git a/src/macros.rs b/src/macros.rs index 9035b0c7d8b..bede00d6e9e 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -19,8 +19,6 @@ // List-like invocations with parentheses will be formatted as function calls, // and those with brackets will be formatted as array literals. -use std::thread; - use syntax::ast; use syntax::parse::token::{Eof, Comma, Token}; use syntax::parse::{ParseSess, tts_to_parser}; @@ -34,12 +32,6 @@ use utils::{wrap_str, span_after}; static FORCED_BRACKET_MACROS: &'static [&'static str] = &["vec!"]; -// We need to pass `TokenTree`s to our expression parsing thread, but they are -// not `Send`. We wrap them in a `Send` container to force our will. -// FIXME: this is a pretty terrible hack. Any other solution would be preferred. -struct ForceSend(pub T); -unsafe impl Send for ForceSend {} - // FIXME: use the enum from libsyntax? #[derive(Clone, Copy)] enum MacroStyle { @@ -81,34 +73,28 @@ pub fn rewrite_macro(mac: &ast::Mac, }; } - let wrapped_tt_vec = ForceSend(mac.node.tts.clone()); - // Wrap expression parsing logic in a thread since the libsyntax parser - // panics on failure, which we do not want to propagate. - // The expression vector is wrapped in an Option inside a Result. - let expr_vec_result = thread::catch_panic(move || { - let parse_session = ParseSess::new(); - let mut parser = tts_to_parser(&parse_session, wrapped_tt_vec.0, vec![]); - let mut expr_vec = vec![]; + let parse_session = ParseSess::new(); + let mut parser = tts_to_parser(&parse_session, mac.node.tts.clone(), Vec::new()); + let mut expr_vec = Vec::new(); - loop { - expr_vec.push(parser.parse_expr()); + loop { + expr_vec.push(match parser.parse_expr_nopanic() { + Ok(expr) => expr, + Err(..) => return None, + }); - match parser.token { - Token::Eof => break, - Token::Comma => (), - _ => panic!("Macro not list-like, skiping..."), - } - - let _ = parser.bump(); - - if parser.token == Token::Eof { - return None; - } + match parser.token { + Token::Eof => break, + Token::Comma => (), + _ => return None, } - Some(expr_vec) - }); - let expr_vec = try_opt!(try_opt!(expr_vec_result.ok())); + let _ = parser.bump(); + + if parser.token == Token::Eof { + return None; + } + } match style { MacroStyle::Parens => { diff --git a/src/utils.rs b/src/utils.rs index 0fd749a5471..fea385532d0 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -75,6 +75,9 @@ pub fn last_line_width(s: &str) -> usize { fn is_skip(meta_item: &MetaItem) -> bool { match meta_item.node { MetaItem_::MetaWord(ref s) => *s == SKIP_ANNOTATION, + MetaItem_::MetaList(ref s, ref l) => { + *s == "cfg_attr" && l.len() == 2 && is_skip(&l[1]) + } _ => false, } } diff --git a/tests/source/macro_not_expr.rs b/tests/source/macro_not_expr.rs new file mode 100644 index 00000000000..d8de4dce38f --- /dev/null +++ b/tests/source/macro_not_expr.rs @@ -0,0 +1,7 @@ +macro_rules! test { + ($($t:tt)*) => {} +} + +fn main() { + test!( a : B => c d ); +} diff --git a/tests/system.rs b/tests/system.rs index b6dee149ed1..649ccdf5647 100644 --- a/tests/system.rs +++ b/tests/system.rs @@ -135,7 +135,6 @@ fn print_mismatches(result: HashMap>) { pub fn idempotent_check(filename: String) -> Result>> { let sig_comments = read_significant_comments(&filename); let mut config = get_config(sig_comments.get("config").map(|x| &(*x)[..])); - let args = vec!["rustfmt".to_owned(), filename]; for (key, val) in &sig_comments { if key != "target" && key != "config" { @@ -146,7 +145,7 @@ pub fn idempotent_check(filename: String) -> Result {} +} + +fn main() { + test!( a : B => c d ); +}