]> git.lizzy.rs Git - rust.git/blob - src/lib.rs
add cli option for color
[rust.git] / src / lib.rs
1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 #![feature(rustc_private)]
12
13 #[macro_use]
14 extern crate derive_new;
15 extern crate diff;
16 #[macro_use]
17 extern crate log;
18 extern crate regex;
19 extern crate rustc_errors as errors;
20 extern crate serde;
21 #[macro_use]
22 extern crate serde_derive;
23 extern crate serde_json;
24 extern crate strings;
25 extern crate syntax;
26 extern crate term;
27 extern crate unicode_segmentation;
28
29 use std::collections::HashMap;
30 use std::fmt;
31 use std::io::{self, stdout, Write};
32 use std::iter::repeat;
33 use std::path::{Path, PathBuf};
34 use std::rc::Rc;
35
36 use errors::{DiagnosticBuilder, Handler};
37 use errors::emitter::{ColorConfig, EmitterWriter};
38 use strings::string_buffer::StringBuffer;
39 use syntax::ast;
40 use syntax::codemap::{CodeMap, FilePathMapping};
41 use syntax::parse::{self, ParseSess};
42
43 use checkstyle::{output_footer, output_header};
44 use config::Config;
45 use filemap::FileMap;
46 use issues::{BadIssueSeeker, Issue};
47 use utils::iscolored;
48 use visitor::FmtVisitor;
49
50 pub use self::summary::Summary;
51
52 #[macro_use]
53 mod utils;
54 mod shape;
55 mod spanned;
56 pub mod config;
57 pub mod codemap;
58 pub mod filemap;
59 pub mod file_lines;
60 pub mod visitor;
61 mod checkstyle;
62 mod closures;
63 mod items;
64 mod missed_spans;
65 mod lists;
66 mod types;
67 mod expr;
68 mod imports;
69 mod issues;
70 mod rewrite;
71 mod string;
72 mod comment;
73 pub mod modules;
74 pub mod rustfmt_diff;
75 mod chains;
76 mod macros;
77 mod patterns;
78 mod summary;
79 mod vertical;
80
81 pub enum ErrorKind {
82     // Line has exceeded character limit (found, maximum)
83     LineOverflow(usize, usize),
84     // Line ends in whitespace
85     TrailingWhitespace,
86     // TO-DO or FIX-ME item without an issue number
87     BadIssue(Issue),
88 }
89
90 impl fmt::Display for ErrorKind {
91     fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
92         match *self {
93             ErrorKind::LineOverflow(found, maximum) => write!(
94                 fmt,
95                 "line exceeded maximum width (maximum: {}, found: {})",
96                 maximum,
97                 found
98             ),
99             ErrorKind::TrailingWhitespace => write!(fmt, "left behind trailing whitespace"),
100             ErrorKind::BadIssue(issue) => write!(fmt, "found {}", issue),
101         }
102     }
103 }
104
105 // Formatting errors that are identified *after* rustfmt has run.
106 pub struct FormattingError {
107     line: usize,
108     kind: ErrorKind,
109     is_comment: bool,
110     line_buffer: String,
111 }
112
113 impl FormattingError {
114     fn msg_prefix(&self) -> &str {
115         match self.kind {
116             ErrorKind::LineOverflow(..) | ErrorKind::TrailingWhitespace => "error:",
117             ErrorKind::BadIssue(_) => "WARNING:",
118         }
119     }
120
121     fn msg_suffix(&self) -> &str {
122         match self.kind {
123             ErrorKind::LineOverflow(..) if self.is_comment => {
124                 "use `error_on_line_overflow_comments = false` to suppress \
125                  the warning against line comments\n"
126             }
127             _ => "",
128         }
129     }
130
131     // (space, target)
132     pub fn format_len(&self) -> (usize, usize) {
133         match self.kind {
134             ErrorKind::LineOverflow(found, max) => (max, found - max),
135             ErrorKind::TrailingWhitespace => {
136                 let trailing_ws_len = self.line_buffer
137                     .chars()
138                     .rev()
139                     .take_while(|c| c.is_whitespace())
140                     .count();
141                 (self.line_buffer.len() - trailing_ws_len, trailing_ws_len)
142             }
143             _ => unreachable!(),
144         }
145     }
146 }
147
148 pub struct FormatReport {
149     // Maps stringified file paths to their associated formatting errors.
150     file_error_map: HashMap<String, Vec<FormattingError>>,
151 }
152
153 impl FormatReport {
154     fn new() -> FormatReport {
155         FormatReport {
156             file_error_map: HashMap::new(),
157         }
158     }
159
160     pub fn warning_count(&self) -> usize {
161         self.file_error_map
162             .iter()
163             .map(|(_, errors)| errors.len())
164             .fold(0, |acc, x| acc + x)
165     }
166
167     pub fn has_warnings(&self) -> bool {
168         self.warning_count() > 0
169     }
170
171     pub fn print_warnings_fancy(
172         &self,
173         mut t: Box<term::Terminal<Output = io::Stderr>>,
174     ) -> Result<(), term::Error> {
175         for (file, errors) in &self.file_error_map {
176             for error in errors {
177                 let prefix_space_len = error.line.to_string().len();
178                 let prefix_spaces: String = repeat(" ").take(1 + prefix_space_len).collect();
179
180                 // First line: the overview of error
181                 t.fg(term::color::RED)?;
182                 t.attr(term::Attr::Bold)?;
183                 write!(t, "{} ", error.msg_prefix())?;
184                 t.reset()?;
185                 t.attr(term::Attr::Bold)?;
186                 write!(t, "{}\n", error.kind)?;
187
188                 // Second line: file info
189                 write!(t, "{}--> ", &prefix_spaces[1..])?;
190                 t.reset()?;
191                 write!(t, "{}:{}\n", file, error.line)?;
192
193                 // Third to fifth lines: show the line which triggered error, if available.
194                 if !error.line_buffer.is_empty() {
195                     let (space_len, target_len) = error.format_len();
196                     t.attr(term::Attr::Bold)?;
197                     write!(t, "{}|\n{} | ", prefix_spaces, error.line)?;
198                     t.reset()?;
199                     write!(t, "{}\n", error.line_buffer)?;
200                     t.attr(term::Attr::Bold)?;
201                     write!(t, "{}| ", prefix_spaces)?;
202                     t.fg(term::color::RED)?;
203                     write!(t, "{}\n", target_str(space_len, target_len))?;
204                     t.reset()?;
205                 }
206
207                 // The last line: show note if available.
208                 let msg_suffix = error.msg_suffix();
209                 if !msg_suffix.is_empty() {
210                     t.attr(term::Attr::Bold)?;
211                     write!(t, "{}= note: ", prefix_spaces)?;
212                     t.reset()?;
213                     write!(t, "{}\n", error.msg_suffix())?;
214                 } else {
215                     write!(t, "\n")?;
216                 }
217                 t.reset()?;
218             }
219         }
220
221         if !self.file_error_map.is_empty() {
222             t.attr(term::Attr::Bold)?;
223             write!(t, "warning: ")?;
224             t.reset()?;
225             write!(
226                 t,
227                 "rustfmt may have failed to format. See previous {} errors.\n\n",
228                 self.warning_count(),
229             )?;
230         }
231
232         Ok(())
233     }
234 }
235
236 fn target_str(space_len: usize, target_len: usize) -> String {
237     let empty_line: String = repeat(" ").take(space_len).collect();
238     let overflowed: String = repeat("^").take(target_len).collect();
239     empty_line + &overflowed
240 }
241
242 impl fmt::Display for FormatReport {
243     // Prints all the formatting errors.
244     fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
245         for (file, errors) in &self.file_error_map {
246             for error in errors {
247                 let prefix_space_len = error.line.to_string().len();
248                 let prefix_spaces: String = repeat(" ").take(1 + prefix_space_len).collect();
249
250                 let error_line_buffer = if error.line_buffer.is_empty() {
251                     String::from(" ")
252                 } else {
253                     let (space_len, target_len) = error.format_len();
254                     format!(
255                         "{}|\n{} | {}\n{}| {}",
256                         prefix_spaces,
257                         error.line,
258                         error.line_buffer,
259                         prefix_spaces,
260                         target_str(space_len, target_len)
261                     )
262                 };
263
264                 let error_info = format!("{} {}", error.msg_prefix(), error.kind);
265                 let file_info = format!("{}--> {}:{}", &prefix_spaces[1..], file, error.line);
266                 let msg_suffix = error.msg_suffix();
267                 let note = if msg_suffix.is_empty() {
268                     String::new()
269                 } else {
270                     format!("{}note= ", prefix_spaces)
271                 };
272
273                 write!(
274                     fmt,
275                     "{}\n{}\n{}\n{}{}\n",
276                     error_info,
277                     file_info,
278                     error_line_buffer,
279                     note,
280                     error.msg_suffix()
281                 )?;
282             }
283         }
284         if !self.file_error_map.is_empty() {
285             write!(
286                 fmt,
287                 "warning: rustfmt may have failed to format. See previous {} errors.\n",
288                 self.warning_count(),
289             )?;
290         }
291         Ok(())
292     }
293 }
294
295 // Formatting which depends on the AST.
296 fn format_ast<F>(
297     krate: &ast::Crate,
298     parse_session: &mut ParseSess,
299     main_file: &Path,
300     config: &Config,
301     mut after_file: F,
302 ) -> Result<(FileMap, bool), io::Error>
303 where
304     F: FnMut(&str, &mut StringBuffer) -> Result<bool, io::Error>,
305 {
306     let mut result = FileMap::new();
307     // diff mode: check if any files are differing
308     let mut has_diff = false;
309
310     // We always skip children for the "Plain" write mode, since there is
311     // nothing to distinguish the nested module contents.
312     let skip_children = config.skip_children() || config.write_mode() == config::WriteMode::Plain;
313     for (path, module) in modules::list_files(krate, parse_session.codemap())? {
314         if skip_children && path.as_path() != main_file {
315             continue;
316         }
317         let path_str = path.to_str().unwrap();
318         if config.verbose() {
319             println!("Formatting {}", path_str);
320         }
321         let mut visitor = FmtVisitor::from_codemap(parse_session, config);
322         let filemap = visitor.codemap.lookup_char_pos(module.inner.lo()).file;
323         // Format inner attributes if available.
324         if !krate.attrs.is_empty() && path == main_file {
325             visitor.skip_empty_lines(filemap.end_pos);
326             if visitor.visit_attrs(&krate.attrs, ast::AttrStyle::Inner) {
327                 visitor.push_rewrite(module.inner, None);
328             } else {
329                 visitor.format_separate_mod(module, &*filemap);
330             }
331         } else {
332             visitor.last_pos = filemap.start_pos;
333             visitor.skip_empty_lines(filemap.end_pos);
334             visitor.format_separate_mod(module, &*filemap);
335         };
336
337         has_diff |= match after_file(path_str, &mut visitor.buffer) {
338             Ok(result) => result,
339             Err(e) => {
340                 // Create a new error with path_str to help users see which files failed
341                 let err_msg = path_str.to_string() + &": ".to_string() + &e.to_string();
342                 return Err(io::Error::new(e.kind(), err_msg));
343             }
344         };
345
346         result.push((path_str.to_owned(), visitor.buffer));
347     }
348
349     Ok((result, has_diff))
350 }
351
352 // Formatting done on a char by char or line by line basis.
353 // FIXME(#209) warn on bad license
354 // FIXME(#20) other stuff for parity with make tidy
355 fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &mut FormatReport) {
356     // Iterate over the chars in the file map.
357     let mut trims = vec![];
358     let mut last_wspace: Option<usize> = None;
359     let mut line_len = 0;
360     let mut cur_line = 1;
361     let mut newline_count = 0;
362     let mut errors = vec![];
363     let mut issue_seeker = BadIssueSeeker::new(config.report_todo(), config.report_fixme());
364     let mut prev_char: Option<char> = None;
365     let mut is_comment = false;
366     let mut line_buffer = String::with_capacity(config.max_width() * 2);
367
368     for (c, b) in text.chars() {
369         if c == '\r' {
370             continue;
371         }
372
373         let format_line = config.file_lines().contains_line(name, cur_line as usize);
374
375         if format_line {
376             // Add warnings for bad todos/ fixmes
377             if let Some(issue) = issue_seeker.inspect(c) {
378                 errors.push(FormattingError {
379                     line: cur_line,
380                     kind: ErrorKind::BadIssue(issue),
381                     is_comment: false,
382                     line_buffer: String::new(),
383                 });
384             }
385         }
386
387         if c == '\n' {
388             if format_line {
389                 // Check for (and record) trailing whitespace.
390                 if let Some(lw) = last_wspace {
391                     trims.push((cur_line, lw, b, line_buffer.clone()));
392                     line_len -= 1;
393                 }
394
395                 // Check for any line width errors we couldn't correct.
396                 let report_error_on_line_overflow = config.error_on_line_overflow()
397                     && (config.error_on_line_overflow_comments() || !is_comment);
398                 if report_error_on_line_overflow && line_len > config.max_width() {
399                     errors.push(FormattingError {
400                         line: cur_line,
401                         kind: ErrorKind::LineOverflow(line_len, config.max_width()),
402                         is_comment: is_comment,
403                         line_buffer: line_buffer.clone(),
404                     });
405                 }
406             }
407
408             line_len = 0;
409             cur_line += 1;
410             newline_count += 1;
411             last_wspace = None;
412             prev_char = None;
413             is_comment = false;
414             line_buffer.clear();
415         } else {
416             newline_count = 0;
417             line_len += 1;
418             if c.is_whitespace() {
419                 if last_wspace.is_none() {
420                     last_wspace = Some(b);
421                 }
422             } else if c == '/' {
423                 if let Some('/') = prev_char {
424                     is_comment = true;
425                 }
426                 last_wspace = None;
427             } else {
428                 last_wspace = None;
429             }
430             prev_char = Some(c);
431             line_buffer.push(c);
432         }
433     }
434
435     if newline_count > 1 {
436         debug!("track truncate: {} {}", text.len, newline_count);
437         let line = text.len - newline_count + 1;
438         text.truncate(line);
439     }
440
441     for &(l, _, _, ref b) in &trims {
442         errors.push(FormattingError {
443             line: l,
444             kind: ErrorKind::TrailingWhitespace,
445             is_comment: false,
446             line_buffer: b.clone(),
447         });
448     }
449
450     report.file_error_map.insert(name.to_owned(), errors);
451 }
452
453 fn parse_input(
454     input: Input,
455     parse_session: &ParseSess,
456 ) -> Result<ast::Crate, Option<DiagnosticBuilder>> {
457     let result = match input {
458         Input::File(file) => {
459             let mut parser = parse::new_parser_from_file(parse_session, &file);
460             parser.cfg_mods = false;
461             parser.parse_crate_mod()
462         }
463         Input::Text(text) => {
464             let mut parser =
465                 parse::new_parser_from_source_str(parse_session, "stdin".to_owned(), text);
466             parser.cfg_mods = false;
467             parser.parse_crate_mod()
468         }
469     };
470
471     match result {
472         Ok(c) => {
473             if parse_session.span_diagnostic.has_errors() {
474                 // Bail out if the parser recovered from an error.
475                 Err(None)
476             } else {
477                 Ok(c)
478             }
479         }
480         Err(e) => Err(Some(e)),
481     }
482 }
483
484 pub fn format_input<T: Write>(
485     input: Input,
486     config: &Config,
487     mut out: Option<&mut T>,
488 ) -> Result<(Summary, FileMap, FormatReport), (io::Error, Summary)> {
489     let mut summary = Summary::default();
490     if config.disable_all_formatting() {
491         // When the input is from stdin, echo back the input.
492         if let Input::Text(ref buf) = input {
493             if let Err(e) = io::stdout().write_all(buf.as_bytes()) {
494                 return Err((e, summary));
495             }
496         }
497         return Ok((summary, FileMap::new(), FormatReport::new()));
498     }
499     let codemap = Rc::new(CodeMap::new(FilePathMapping::empty()));
500
501     let tty_handler =
502         Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(codemap.clone()));
503     let mut parse_session = ParseSess::with_span_handler(tty_handler, codemap.clone());
504
505     let main_file = match input {
506         Input::File(ref file) => file.clone(),
507         Input::Text(..) => PathBuf::from("stdin"),
508     };
509
510     let krate = match parse_input(input, &parse_session) {
511         Ok(krate) => krate,
512         Err(diagnostic) => {
513             if let Some(mut diagnostic) = diagnostic {
514                 diagnostic.emit();
515             }
516             summary.add_parsing_error();
517             return Ok((summary, FileMap::new(), FormatReport::new()));
518         }
519     };
520
521     if parse_session.span_diagnostic.has_errors() {
522         summary.add_parsing_error();
523     }
524
525     // Suppress error output after parsing.
526     let silent_emitter = Box::new(EmitterWriter::new(
527         Box::new(Vec::new()),
528         Some(codemap.clone()),
529         false,
530     ));
531     parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter);
532
533     let mut report = FormatReport::new();
534
535     match format_ast(
536         &krate,
537         &mut parse_session,
538         &main_file,
539         config,
540         |file_name, file| {
541             // For some reason, the codemap does not include terminating
542             // newlines so we must add one on for each file. This is sad.
543             filemap::append_newline(file);
544
545             format_lines(file, file_name, config, &mut report);
546
547             if let Some(ref mut out) = out {
548                 return filemap::write_file(file, file_name, out, config);
549             }
550             Ok(false)
551         },
552     ) {
553         Ok((file_map, has_diff)) => {
554             if report.has_warnings() {
555                 summary.add_formatting_error();
556             }
557
558             if has_diff {
559                 summary.add_diff();
560             }
561
562             Ok((summary, file_map, report))
563         }
564         Err(e) => Err((e, summary)),
565     }
566 }
567
568 #[derive(Debug)]
569 pub enum Input {
570     File(PathBuf),
571     Text(String),
572 }
573
574 pub fn run(input: Input, config: &Config) -> Summary {
575     let out = &mut stdout();
576     output_header(out, config.write_mode()).ok();
577     match format_input(input, config, Some(out)) {
578         Ok((summary, _, report)) => {
579             output_footer(out, config.write_mode()).ok();
580
581             if report.has_warnings() {
582                 match term::stderr() {
583                     Some(ref t)
584                         if iscolored(config.color()) && t.supports_color()
585                             && t.supports_attr(term::Attr::Bold) =>
586                     {
587                         match report.print_warnings_fancy(term::stderr().unwrap()) {
588                             Ok(..) => (),
589                             Err(..) => panic!("Unable to write to stderr: {}", report),
590                         }
591                     }
592                     _ => msg!("{}", report),
593                 }
594             }
595
596             summary
597         }
598         Err((msg, mut summary)) => {
599             msg!("Error writing files: {}", msg);
600             summary.add_operational_error();
601             summary
602         }
603     }
604 }