]> git.lizzy.rs Git - rust.git/blob - src/lib.rs
default required-version to the current rustfmt version
[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             _ => (0, 0), // 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             visitor.visit_attrs(&krate.attrs, ast::AttrStyle::Inner);
323         } else {
324             visitor.last_pos = filemap.start_pos;
325         }
326         visitor.format_separate_mod(module, &*filemap);
327
328         has_diff |= after_file(path_str, &mut visitor.buffer)?;
329
330         result.push((path_str.to_owned(), visitor.buffer));
331     }
332
333     Ok((result, has_diff))
334 }
335
336 // Formatting done on a char by char or line by line basis.
337 // FIXME(#209) warn on bad license
338 // FIXME(#20) other stuff for parity with make tidy
339 fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &mut FormatReport) {
340     // Iterate over the chars in the file map.
341     let mut trims = vec![];
342     let mut last_wspace: Option<usize> = None;
343     let mut line_len = 0;
344     let mut cur_line = 1;
345     let mut newline_count = 0;
346     let mut errors = vec![];
347     let mut issue_seeker = BadIssueSeeker::new(config.report_todo(), config.report_fixme());
348     let mut prev_char: Option<char> = None;
349     let mut is_comment = false;
350     let mut line_buffer = String::with_capacity(config.max_width() * 2);
351
352     for (c, b) in text.chars() {
353         if c == '\r' {
354             continue;
355         }
356
357         let format_line = config.file_lines().contains_line(name, cur_line as usize);
358
359         if format_line {
360             // Add warnings for bad todos/ fixmes
361             if let Some(issue) = issue_seeker.inspect(c) {
362                 errors.push(FormattingError {
363                     line: cur_line,
364                     kind: ErrorKind::BadIssue(issue),
365                     is_comment: false,
366                     line_buffer: String::new(),
367                 });
368             }
369         }
370
371         if c == '\n' {
372             if format_line {
373                 // Check for (and record) trailing whitespace.
374                 if let Some(lw) = last_wspace {
375                     trims.push((cur_line, lw, b, line_buffer.clone()));
376                     line_len -= 1;
377                 }
378
379                 // Check for any line width errors we couldn't correct.
380                 let report_error_on_line_overflow = config.error_on_line_overflow()
381                     && (config.error_on_line_overflow_comments() || !is_comment);
382                 if report_error_on_line_overflow && line_len > config.max_width() {
383                     errors.push(FormattingError {
384                         line: cur_line,
385                         kind: ErrorKind::LineOverflow(line_len, config.max_width()),
386                         is_comment: is_comment,
387                         line_buffer: line_buffer.clone(),
388                     });
389                 }
390             }
391
392             line_len = 0;
393             cur_line += 1;
394             newline_count += 1;
395             last_wspace = None;
396             prev_char = None;
397             is_comment = false;
398             line_buffer.clear();
399         } else {
400             newline_count = 0;
401             line_len += 1;
402             if c.is_whitespace() {
403                 if last_wspace.is_none() {
404                     last_wspace = Some(b);
405                 }
406             } else if c == '/' {
407                 if let Some('/') = prev_char {
408                     is_comment = true;
409                 }
410                 last_wspace = None;
411             } else {
412                 last_wspace = None;
413             }
414             prev_char = Some(c);
415             line_buffer.push(c);
416         }
417     }
418
419     if newline_count > 1 {
420         debug!("track truncate: {} {}", text.len, newline_count);
421         let line = text.len - newline_count + 1;
422         text.truncate(line);
423     }
424
425     for &(l, _, _, ref b) in &trims {
426         errors.push(FormattingError {
427             line: l,
428             kind: ErrorKind::TrailingWhitespace,
429             is_comment: false,
430             line_buffer: b.clone(),
431         });
432     }
433
434     report.file_error_map.insert(name.to_owned(), errors);
435 }
436
437 fn parse_input(
438     input: Input,
439     parse_session: &ParseSess,
440 ) -> Result<ast::Crate, Option<DiagnosticBuilder>> {
441     let result = match input {
442         Input::File(file) => {
443             let mut parser = parse::new_parser_from_file(parse_session, &file);
444             parser.cfg_mods = false;
445             parser.parse_crate_mod()
446         }
447         Input::Text(text) => {
448             let mut parser =
449                 parse::new_parser_from_source_str(parse_session, "stdin".to_owned(), text);
450             parser.cfg_mods = false;
451             parser.parse_crate_mod()
452         }
453     };
454
455     match result {
456         Ok(c) => {
457             if parse_session.span_diagnostic.has_errors() {
458                 // Bail out if the parser recovered from an error.
459                 Err(None)
460             } else {
461                 Ok(c)
462             }
463         }
464         Err(e) => Err(Some(e)),
465     }
466 }
467
468 pub fn format_input<T: Write>(
469     input: Input,
470     config: &Config,
471     mut out: Option<&mut T>,
472 ) -> Result<(Summary, FileMap, FormatReport), (io::Error, Summary)> {
473     let mut summary = Summary::default();
474     if config.disable_all_formatting() {
475         // When the input is from stdin, echo back the input.
476         if let Input::Text(ref buf) = input {
477             if let Err(e) = io::stdout().write_all(buf.as_bytes()) {
478                 return Err((e, summary));
479             }
480         }
481         return Ok((summary, FileMap::new(), FormatReport::new()));
482     }
483     let codemap = Rc::new(CodeMap::new(FilePathMapping::empty()));
484
485     let tty_handler =
486         Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(codemap.clone()));
487     let mut parse_session = ParseSess::with_span_handler(tty_handler, codemap.clone());
488
489     let main_file = match input {
490         Input::File(ref file) => file.clone(),
491         Input::Text(..) => PathBuf::from("stdin"),
492     };
493
494     let krate = match parse_input(input, &parse_session) {
495         Ok(krate) => krate,
496         Err(diagnostic) => {
497             if let Some(mut diagnostic) = diagnostic {
498                 diagnostic.emit();
499             }
500             summary.add_parsing_error();
501             return Ok((summary, FileMap::new(), FormatReport::new()));
502         }
503     };
504
505     if parse_session.span_diagnostic.has_errors() {
506         summary.add_parsing_error();
507     }
508
509     // Suppress error output after parsing.
510     let silent_emitter = Box::new(EmitterWriter::new(
511         Box::new(Vec::new()),
512         Some(codemap.clone()),
513     ));
514     parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter);
515
516     let mut report = FormatReport::new();
517
518     match format_ast(
519         &krate,
520         &mut parse_session,
521         &main_file,
522         config,
523         |file_name, file| {
524             // For some reason, the codemap does not include terminating
525             // newlines so we must add one on for each file. This is sad.
526             filemap::append_newline(file);
527
528             format_lines(file, file_name, config, &mut report);
529
530             if let Some(ref mut out) = out {
531                 return filemap::write_file(file, file_name, out, config);
532             }
533             Ok(false)
534         },
535     ) {
536         Ok((file_map, has_diff)) => {
537             if report.has_warnings() {
538                 summary.add_formatting_error();
539             }
540
541             if has_diff {
542                 summary.add_diff();
543             }
544
545             Ok((summary, file_map, report))
546         }
547         Err(e) => Err((e, summary)),
548     }
549 }
550
551 #[derive(Debug)]
552 pub enum Input {
553     File(PathBuf),
554     Text(String),
555 }
556
557 pub fn run(input: Input, config: &Config) -> Summary {
558     let out = &mut stdout();
559     output_header(out, config.write_mode()).ok();
560     match format_input(input, config, Some(out)) {
561         Ok((summary, _, report)) => {
562             output_footer(out, config.write_mode()).ok();
563
564             if report.has_warnings() {
565                 match term::stderr() {
566                     Some(ref t) if isatty() && t.supports_color() => {
567                         match report.print_warnings_fancy(term::stderr().unwrap()) {
568                             Ok(..) => (),
569                             Err(..) => panic!("Unable to write to stderr: {}", report),
570                         }
571                     }
572                     _ => msg!("{}", report),
573                 }
574             }
575
576             summary
577         }
578         Err((msg, mut summary)) => {
579             msg!("Error writing files: {}", msg);
580             summary.add_operational_error();
581             summary
582         }
583     }
584 }