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