use std::io::{Write, stdout};
use WriteMode;
use NewlineStyle;
+use utils::round_up_to_power_of_two;
// This is basically a wrapper around a bunch of Ropes which makes it convenient
// to work with libsyntax. It is badly named.
for f in codemap.files.borrow().iter() {
// Use the length of the file as a heuristic for how much space we
- // need. I hope that at some stage someone rounds this up to the next
- // power of two. TODO check that or do it here.
- result.file_map.insert(f.name.clone(),
- StringBuffer::with_capacity(f.src.as_ref().unwrap().len()));
+ // need. Round to the next power of two.
+ let buffer_cap = round_up_to_power_of_two(f.src.as_ref().unwrap().len());
+ result.file_map.insert(f.name.clone(), StringBuffer::with_capacity(buffer_cap));
result.file_spans.push((f.start_pos.0, f.end_pos.0));
}
extern crate toml;
+use {NewlineStyle, BraceStyle, ReturnIndent};
+use lists::SeparatorTactic;
+use issues::ReportTactic;
+
#[derive(RustcDecodable)]
pub struct Config {
pub max_width: usize,
pub ideal_width: usize,
pub leeway: usize,
pub tab_spaces: usize,
- pub newline_style: ::NewlineStyle,
- pub fn_brace_style: ::BraceStyle,
- pub fn_return_indent: ::ReturnIndent,
+ pub newline_style: NewlineStyle,
+ pub fn_brace_style: BraceStyle,
+ pub fn_return_indent: ReturnIndent,
pub fn_args_paren_newline: bool,
pub struct_trailing_comma: bool,
- pub struct_lit_trailing_comma: ::lists::SeparatorTactic,
+ pub struct_lit_trailing_comma: SeparatorTactic,
pub enum_trailing_comma: bool,
+ pub report_todo: ReportTactic,
+ pub report_fixme: ReportTactic,
}
impl Config {
struct_trailing_comma = true
struct_lit_trailing_comma = "Vertical"
enum_trailing_comma = true
+report_todo = "Always"
+report_fixme = "Never"
--- /dev/null
+// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Objects for seeking through a char stream for occurences of TODO and FIXME.
+// Depending on the loaded configuration, may also check that these have an
+// associated issue number.
+
+use std::fmt;
+
+static TO_DO_CHARS: &'static [char] = &['T', 'O', 'D', 'O'];
+static FIX_ME_CHARS: &'static [char] = &['F', 'I', 'X', 'M', 'E'];
+
+#[derive(Clone, Copy)]
+pub enum ReportTactic {
+ Always,
+ Unnumbered,
+ Never
+}
+
+impl ReportTactic {
+ fn is_enabled(&self) -> bool {
+ match *self {
+ ReportTactic::Always => true,
+ ReportTactic::Unnumbered => true,
+ ReportTactic::Never => false
+ }
+ }
+}
+
+impl_enum_decodable!(ReportTactic, Always, Unnumbered, Never);
+
+#[derive(Clone, Copy)]
+enum Seeking {
+ Issue {
+ todo_idx: usize,
+ fixme_idx: usize
+ },
+ Number {
+ issue: Issue,
+ part: NumberPart
+ }
+}
+
+#[derive(Clone, Copy)]
+enum NumberPart {
+ OpenParen,
+ Pound,
+ Number,
+ CloseParen
+}
+
+#[derive(PartialEq, Eq, Debug, Clone, Copy)]
+pub struct Issue {
+ issue_type: IssueType,
+ // Indicates whether we're looking for issues with missing numbers, or
+ // all issues of this type.
+ missing_number: bool,
+}
+
+impl fmt::Display for Issue {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ let msg = match self.issue_type {
+ IssueType::Todo => "TODO",
+ IssueType::Fixme => "FIXME",
+ };
+ let details = if self.missing_number { " without issue number" } else { "" };
+
+ write!(fmt, "{}{}", msg, details)
+ }
+}
+
+#[derive(PartialEq, Eq, Debug, Clone, Copy)]
+enum IssueType {
+ Todo,
+ Fixme
+}
+
+enum IssueClassification {
+ Good,
+ Bad(Issue),
+ None
+}
+
+pub struct BadIssueSeeker {
+ state: Seeking,
+ report_todo: ReportTactic,
+ report_fixme: ReportTactic,
+}
+
+impl BadIssueSeeker {
+ pub fn new(report_todo: ReportTactic, report_fixme: ReportTactic) -> BadIssueSeeker {
+ BadIssueSeeker {
+ state: Seeking::Issue { todo_idx: 0, fixme_idx: 0 },
+ report_todo: report_todo,
+ report_fixme: report_fixme,
+ }
+ }
+
+ // Check whether or not the current char is conclusive evidence for an
+ // unnumbered TO-DO or FIX-ME.
+ pub fn inspect(&mut self, c: char) -> Option<Issue> {
+ match self.state {
+ Seeking::Issue { todo_idx, fixme_idx } => {
+ self.state = self.inspect_issue(c, todo_idx, fixme_idx);
+ },
+ Seeking::Number { issue, part } => {
+ let result = self.inspect_number(c, issue, part);
+
+ if let IssueClassification::None = result {
+ return None;
+ }
+
+ self.state = Seeking::Issue { todo_idx: 0, fixme_idx: 0 };
+
+ if let IssueClassification::Bad(issue) = result {
+ return Some(issue);
+ }
+ }
+ }
+
+ None
+ }
+
+ fn inspect_issue(&mut self, c: char, mut todo_idx: usize, mut fixme_idx: usize) -> Seeking {
+ // FIXME: Should we also check for lower case characters?
+ if self.report_todo.is_enabled() && c == TO_DO_CHARS[todo_idx] {
+ todo_idx += 1;
+ if todo_idx == TO_DO_CHARS.len() {
+ return Seeking::Number {
+ issue: Issue {
+ issue_type: IssueType::Todo,
+ missing_number: if let ReportTactic::Unnumbered = self.report_todo {
+ true
+ } else {
+ false
+ }
+ },
+ part: NumberPart::OpenParen
+ };
+ }
+ fixme_idx = 0;
+ } else if self.report_fixme.is_enabled() && c == FIX_ME_CHARS[fixme_idx] {
+ // Exploit the fact that the character sets of todo and fixme
+ // are disjoint by adding else.
+ fixme_idx += 1;
+ if fixme_idx == FIX_ME_CHARS.len() {
+ return Seeking::Number {
+ issue: Issue {
+ issue_type: IssueType::Fixme,
+ missing_number: if let ReportTactic::Unnumbered = self.report_fixme {
+ true
+ } else {
+ false
+ }
+ },
+ part: NumberPart::OpenParen
+ };
+ }
+ todo_idx = 0;
+ } else {
+ todo_idx = 0;
+ fixme_idx = 0;
+ }
+
+ Seeking::Issue { todo_idx: todo_idx, fixme_idx: fixme_idx }
+ }
+
+ fn inspect_number(&mut self,
+ c: char,
+ issue: Issue,
+ mut part: NumberPart)
+ -> IssueClassification
+ {
+ if ! issue.missing_number || c == '\n' {
+ return IssueClassification::Bad(issue);
+ } else if c == ')' {
+ return if let NumberPart::CloseParen = part {
+ IssueClassification::Good
+ } else {
+ IssueClassification::Bad(issue)
+ };
+ }
+
+ match part {
+ NumberPart::OpenParen => {
+ if c != '(' {
+ return IssueClassification::Bad(issue);
+ } else {
+ part = NumberPart::Pound;
+ }
+ },
+ NumberPart::Pound => {
+ if c == '#' {
+ part = NumberPart::Number;
+ }
+ },
+ NumberPart::Number => {
+ if c >= '0' && c <= '9' {
+ part = NumberPart::CloseParen;
+ } else {
+ return IssueClassification::Bad(issue);
+ }
+ },
+ NumberPart::CloseParen => {}
+ }
+
+ self.state = Seeking::Number {
+ part: part,
+ issue: issue
+ };
+
+ IssueClassification::None
+ }
+}
+
+#[test]
+fn find_unnumbered_issue() {
+ fn check_fail(text: &str, failing_pos: usize) {
+ println!("{:?}", text);
+ let mut seeker = BadIssueSeeker::new(ReportTactic::Unnumbered, ReportTactic::Unnumbered);
+ assert_eq!(Some(failing_pos), text.chars().position(|c| seeker.inspect(c).is_some()));
+ }
+
+ fn check_pass(text: &str) {
+ let mut seeker = BadIssueSeeker::new(ReportTactic::Unnumbered, ReportTactic::Unnumbered);
+ assert_eq!(None, text.chars().position(|c| seeker.inspect(c).is_some()));
+ }
+
+ check_fail("TODO\n", 4);
+ check_pass(" TO FIX DOME\n");
+ check_fail(" \n FIXME\n", 8);
+ check_fail("FIXME(\n", 6);
+ check_fail("FIXME(#\n", 7);
+ check_fail("FIXME(#1\n", 8);
+ check_fail("FIXME(#)1\n", 7);
+ check_pass("FIXME(#1222)\n");
+ check_fail("FIXME(#12\n22)\n", 9);
+ check_pass("FIXME(@maintainer, #1222, hello)\n");
+ check_fail("TODO(#22) FIXME\n", 15);
+}
+
+#[test]
+fn find_issue() {
+ fn is_bad_issue(text: &str, report_todo: ReportTactic, report_fixme: ReportTactic) -> bool {
+ let mut seeker = BadIssueSeeker::new(report_todo, report_fixme);
+ text.chars().any(|c| seeker.inspect(c).is_some())
+ }
+
+ assert!(is_bad_issue("TODO(@maintainer, #1222, hello)\n",
+ ReportTactic::Always,
+ ReportTactic::Never));
+
+ assert!(! is_bad_issue("TODO: no number\n",
+ ReportTactic::Never,
+ ReportTactic::Always));
+
+ assert!(is_bad_issue("This is a FIXME(#1)\n",
+ ReportTactic::Never,
+ ReportTactic::Always));
+
+ assert!(! is_bad_issue("bad FIXME\n",
+ ReportTactic::Always,
+ ReportTactic::Never));
+}
+
+#[test]
+fn issue_type() {
+ let mut seeker = BadIssueSeeker::new(ReportTactic::Always, ReportTactic::Never);
+ let expected = Some(Issue {
+ issue_type: IssueType::Todo,
+ missing_number: false
+ });
+
+ assert_eq!(expected,
+ "TODO(#100): more awesomeness".chars()
+ .map(|c| seeker.inspect(c))
+ .find(Option::is_some)
+ .unwrap());
+
+ let mut seeker = BadIssueSeeker::new(ReportTactic::Never, ReportTactic::Unnumbered);
+ let expected = Some(Issue {
+ issue_type: IssueType::Fixme,
+ missing_number: true
+ });
+
+ assert_eq!(expected,
+ "Test. FIXME: bad, bad, not good".chars()
+ .map(|c| seeker.inspect(c))
+ .find(Option::is_some)
+ .unwrap());
+}
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 issues::{BadIssueSeeker, Issue};
use changes::ChangeSet;
use visitor::FmtVisitor;
mod types;
mod expr;
mod imports;
+mod issues;
const MIN_STRING: usize = 10;
// When we get scoped annotations, we should have rustfmt::skip.
impl_enum_decodable!(ReturnIndent, WithArgs, WithWhereClause);
+enum ErrorKind {
+ // Line has more than config!(max_width) characters
+ 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) -> ChangeSet<'a> {
let mut visitor = FmtVisitor::from_codemap(codemap);
}
// 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) -> 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 {
}
// 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));
+ 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 {
// 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));
let result = changes.write_all_files(write_mode);
// except according to those terms.
use utils::make_indent;
-use rustc_serialize::{Decodable, Decoder};
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
pub enum ListTactic {
}
}
+#[inline]
+#[cfg(target_pointer_width="64")]
+// Based on the trick layed out at
+// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
+pub fn round_up_to_power_of_two(mut x: usize) -> usize {
+ x -= 1;
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ x |= x >> 8;
+ x |= x >> 16;
+ x |= x >> 32;
+ x + 1
+}
+
+#[cfg(target_pointer_width="32")]
+pub fn round_up_to_power_of_two(mut x: usize) -> usize {
+ x -= 1;
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ x |= x >> 8;
+ x |= x >> 16;
+ x + 1
+}
+
// Macro for deriving implementations of Decodable for enums
#[macro_export]
macro_rules! impl_enum_decodable {
( $e:ident, $( $x:ident ),* ) => {
- impl Decodable for $e {
- fn decode<D: Decoder>(d: &mut D) -> Result<Self, D::Error> {
+ impl ::rustc_serialize::Decodable for $e {
+ fn decode<D: ::rustc_serialize::Decoder>(d: &mut D) -> Result<Self, D::Error> {
let s = try!(d.read_str());
match &*s {
$(
}
};
}
+
+#[test]
+fn power_rounding() {
+ assert_eq!(1, round_up_to_power_of_two(1));
+ assert_eq!(64, round_up_to_power_of_two(33));
+ assert_eq!(256, round_up_to_power_of_two(256));
+}
struct_trailing_comma = true
struct_lit_trailing_comma = "Vertical"
enum_trailing_comma = true
+report_todo = "Always"
+report_fixme = "Never"