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