]> git.lizzy.rs Git - rust.git/blob - src/formatting.rs
71ebe9d41026756e7ec9a4eb241b8176582ca4a5
[rust.git] / src / formatting.rs
1 // High level formatting functions.
2
3 use std::collections::HashMap;
4 use std::io::{self, Write};
5 use std::panic::{catch_unwind, AssertUnwindSafe};
6 use std::rc::Rc;
7 use std::time::{Duration, Instant};
8
9 use syntax::ast;
10 use syntax::codemap::{CodeMap, FilePathMapping, Span};
11 use syntax::errors::emitter::{ColorConfig, EmitterWriter};
12 use syntax::errors::{DiagnosticBuilder, Handler};
13 use syntax::parse::{self, ParseSess};
14
15 use comment::{CharClasses, FullCodeCharKind};
16 use config::{Config, FileName, NewlineStyle, Verbosity};
17 use issues::BadIssueSeeker;
18 use visitor::{FmtVisitor, SnippetProvider};
19 use {filemap, modules, ErrorKind, FormatReport, Input, Session};
20
21 // A map of the files of a crate, with their new content
22 pub(crate) type FileMap = Vec<FileRecord>;
23 pub(crate) type FileRecord = (FileName, String);
24
25 pub(crate) struct FormattingError {
26     pub(crate) line: usize,
27     pub(crate) kind: ErrorKind,
28     is_comment: bool,
29     is_string: bool,
30     pub(crate) line_buffer: String,
31 }
32
33 impl FormattingError {
34     pub(crate) fn from_span(span: &Span, codemap: &CodeMap, kind: ErrorKind) -> FormattingError {
35         FormattingError {
36             line: codemap.lookup_char_pos(span.lo()).line,
37             is_comment: kind.is_comment(),
38             kind,
39             is_string: false,
40             line_buffer: codemap
41                 .span_to_lines(*span)
42                 .ok()
43                 .and_then(|fl| {
44                     fl.file
45                         .get_line(fl.lines[0].line_index)
46                         .map(|l| l.into_owned())
47                 })
48                 .unwrap_or_else(|| String::new()),
49         }
50     }
51
52     pub(crate) fn msg_prefix(&self) -> &str {
53         match self.kind {
54             ErrorKind::LineOverflow(..)
55             | ErrorKind::TrailingWhitespace
56             | ErrorKind::IoError(_)
57             | ErrorKind::ParseError
58             | ErrorKind::LostComment => "internal error:",
59             ErrorKind::LicenseCheck | ErrorKind::BadAttr | ErrorKind::VersionMismatch => "error:",
60             ErrorKind::BadIssue(_) | ErrorKind::DeprecatedAttr => "warning:",
61         }
62     }
63
64     pub(crate) fn msg_suffix(&self) -> &str {
65         if self.is_comment || self.is_string {
66             "set `error_on_unformatted = false` to suppress \
67              the warning against comments or string literals\n"
68         } else {
69             ""
70         }
71     }
72
73     // (space, target)
74     pub(crate) fn format_len(&self) -> (usize, usize) {
75         match self.kind {
76             ErrorKind::LineOverflow(found, max) => (max, found - max),
77             ErrorKind::TrailingWhitespace
78             | ErrorKind::DeprecatedAttr
79             | ErrorKind::BadAttr
80             | ErrorKind::LostComment => {
81                 let trailing_ws_start = self
82                     .line_buffer
83                     .rfind(|c: char| !c.is_whitespace())
84                     .map(|pos| pos + 1)
85                     .unwrap_or(0);
86                 (
87                     trailing_ws_start,
88                     self.line_buffer.len() - trailing_ws_start,
89                 )
90             }
91             _ => unreachable!(),
92         }
93     }
94 }
95
96 pub(crate) type FormatErrorMap = HashMap<FileName, Vec<FormattingError>>;
97
98 #[derive(Default, Debug)]
99 pub(crate) struct ReportedErrors {
100     pub(crate) has_operational_errors: bool,
101     pub(crate) has_check_errors: bool,
102 }
103
104 fn should_emit_verbose<F>(is_stdin: bool, config: &Config, f: F)
105 where
106     F: Fn(),
107 {
108     if config.verbose() == Verbosity::Verbose && !is_stdin {
109         f();
110     }
111 }
112
113 /// Returns true if the line with the given line number was skipped by `#[rustfmt::skip]`.
114 fn is_skipped_line(line_number: usize, skipped_range: &[(usize, usize)]) -> bool {
115     skipped_range
116         .iter()
117         .any(|&(lo, hi)| lo <= line_number && line_number <= hi)
118 }
119
120 fn should_report_error(
121     config: &Config,
122     char_kind: FullCodeCharKind,
123     is_string: bool,
124     error_kind: &ErrorKind,
125 ) -> bool {
126     let allow_error_report = if char_kind.is_comment() || is_string || error_kind.is_comment() {
127         config.error_on_unformatted()
128     } else {
129         true
130     };
131
132     match error_kind {
133         ErrorKind::LineOverflow(..) => config.error_on_line_overflow() && allow_error_report,
134         ErrorKind::TrailingWhitespace | ErrorKind::LostComment => allow_error_report,
135         _ => true,
136     }
137 }
138
139 // Formatting done on a char by char or line by line basis.
140 // FIXME(#20) other stuff for parity with make tidy
141 fn format_lines(
142     text: &mut String,
143     name: &FileName,
144     skipped_range: &[(usize, usize)],
145     config: &Config,
146     report: &FormatReport,
147 ) {
148     let mut last_was_space = false;
149     let mut line_len = 0;
150     let mut cur_line = 1;
151     let mut newline_count = 0;
152     let mut errors = vec![];
153     let mut issue_seeker = BadIssueSeeker::new(config.report_todo(), config.report_fixme());
154     let mut line_buffer = String::with_capacity(config.max_width() * 2);
155     let mut is_string = false; // true if the current line contains a string literal.
156     let mut format_line = config.file_lines().contains_line(name, cur_line);
157     let allow_issue_seek = !issue_seeker.is_disabled();
158
159     // Check license.
160     if let Some(ref license_template) = config.license_template {
161         if !license_template.is_match(text) {
162             errors.push(FormattingError {
163                 line: cur_line,
164                 kind: ErrorKind::LicenseCheck,
165                 is_comment: false,
166                 is_string: false,
167                 line_buffer: String::new(),
168             });
169         }
170     }
171
172     // Iterate over the chars in the file map.
173     for (kind, c) in CharClasses::new(text.chars()) {
174         if c == '\r' {
175             continue;
176         }
177
178         if allow_issue_seek && format_line {
179             // Add warnings for bad todos/ fixmes
180             if let Some(issue) = issue_seeker.inspect(c) {
181                 errors.push(FormattingError {
182                     line: cur_line,
183                     kind: ErrorKind::BadIssue(issue),
184                     is_comment: false,
185                     is_string: false,
186                     line_buffer: String::new(),
187                 });
188             }
189         }
190
191         if c == '\n' {
192             if format_line {
193                 // Check for (and record) trailing whitespace.
194                 if last_was_space {
195                     if should_report_error(config, kind, is_string, &ErrorKind::TrailingWhitespace)
196                         && !is_skipped_line(cur_line, skipped_range)
197                     {
198                         errors.push(FormattingError {
199                             line: cur_line,
200                             kind: ErrorKind::TrailingWhitespace,
201                             is_comment: kind.is_comment(),
202                             is_string: kind.is_string(),
203                             line_buffer: line_buffer.clone(),
204                         });
205                     }
206                     line_len -= 1;
207                 }
208
209                 // Check for any line width errors we couldn't correct.
210                 let error_kind = ErrorKind::LineOverflow(line_len, config.max_width());
211                 if line_len > config.max_width()
212                     && !is_skipped_line(cur_line, skipped_range)
213                     && should_report_error(config, kind, is_string, &error_kind)
214                 {
215                     errors.push(FormattingError {
216                         line: cur_line,
217                         kind: error_kind,
218                         is_comment: kind.is_comment(),
219                         is_string,
220                         line_buffer: line_buffer.clone(),
221                     });
222                 }
223             }
224
225             line_len = 0;
226             cur_line += 1;
227             format_line = config.file_lines().contains_line(name, cur_line);
228             newline_count += 1;
229             last_was_space = false;
230             line_buffer.clear();
231             is_string = false;
232         } else {
233             newline_count = 0;
234             line_len += if c == '\t' { config.tab_spaces() } else { 1 };
235             last_was_space = c.is_whitespace();
236             line_buffer.push(c);
237             if kind.is_string() {
238                 is_string = true;
239             }
240         }
241     }
242
243     if newline_count > 1 {
244         debug!("track truncate: {} {}", text.len(), newline_count);
245         let line = text.len() - newline_count + 1;
246         text.truncate(line);
247     }
248
249     report.append(name.clone(), errors);
250 }
251
252 fn parse_input<'sess>(
253     input: Input,
254     parse_session: &'sess ParseSess,
255     config: &Config,
256 ) -> Result<ast::Crate, ParseError<'sess>> {
257     let mut parser = match input {
258         Input::File(file) => parse::new_parser_from_file(parse_session, &file),
259         Input::Text(text) => parse::new_parser_from_source_str(
260             parse_session,
261             syntax::codemap::FileName::Custom("stdin".to_owned()),
262             text,
263         ),
264     };
265
266     parser.cfg_mods = false;
267     if config.skip_children() {
268         parser.recurse_into_file_modules = false;
269     }
270
271     let mut parser = AssertUnwindSafe(parser);
272     let result = catch_unwind(move || parser.0.parse_crate_mod());
273
274     match result {
275         Ok(Ok(c)) => {
276             if parse_session.span_diagnostic.has_errors() {
277                 // Bail out if the parser recovered from an error.
278                 Err(ParseError::Recovered)
279             } else {
280                 Ok(c)
281             }
282         }
283         Ok(Err(e)) => Err(ParseError::Error(e)),
284         Err(_) => Err(ParseError::Panic),
285     }
286 }
287
288 /// All the ways that parsing can fail.
289 enum ParseError<'sess> {
290     /// There was an error, but the parser recovered.
291     Recovered,
292     /// There was an error (supplied) and parsing failed.
293     Error(DiagnosticBuilder<'sess>),
294     /// The parser panicked.
295     Panic,
296 }
297
298 impl<'b, T: Write + 'b> Session<'b, T> {
299     pub(crate) fn format_input_inner(&mut self, input: Input) -> Result<FormatReport, ErrorKind> {
300         if !self.config.version_meets_requirement() {
301             return Err(ErrorKind::VersionMismatch);
302         }
303
304         syntax::with_globals(|| {
305             syntax_pos::hygiene::set_default_edition(
306                 self.config.edition().to_libsyntax_pos_edition(),
307             );
308
309             if self.config.disable_all_formatting() {
310                 // When the input is from stdin, echo back the input.
311                 if let Input::Text(ref buf) = input {
312                     if let Err(e) = io::stdout().write_all(buf.as_bytes()) {
313                         return Err(From::from(e));
314                     }
315                 }
316                 return Ok(FormatReport::new());
317             }
318
319             let config = &self.config.clone();
320             let format_result = format_project(input, config, self);
321
322             format_result.map(|(report, summary)| {
323                 self.summary.add(summary);
324                 report
325             })
326         })
327     }
328 }
329
330 // Handle the results of formatting.
331 trait FormatHandler {
332     fn handle_formatted_file(&mut self, path: FileName, result: String) -> Result<(), ErrorKind>;
333 }
334
335 impl<'b, T: Write + 'b> FormatHandler for Session<'b, T> {
336     // Called for each formatted file.
337     fn handle_formatted_file(
338         &mut self,
339         path: FileName,
340         mut result: String,
341     ) -> Result<(), ErrorKind> {
342         if let Some(ref mut out) = self.out {
343             match filemap::write_file(&mut result, &path, out, &self.config) {
344                 Ok(b) if b => self.summary.add_diff(),
345                 Err(e) => {
346                     // Create a new error with path_str to help users see which files failed
347                     let err_msg = format!("{}: {}", path, e);
348                     return Err(io::Error::new(e.kind(), err_msg).into());
349                 }
350                 _ => {}
351             }
352         }
353
354         self.filemap.push((path, result));
355         Ok(())
356     }
357 }
358
359 // Format an entire crate (or subset of the module tree).
360 fn format_project<T: FormatHandler>(
361     input: Input,
362     config: &Config,
363     handler: &mut T,
364 ) -> Result<(FormatReport, Summary), ErrorKind> {
365     let mut summary = Summary::default();
366     let mut timer = Timer::Initialized(Instant::now());
367
368     let input_is_stdin = input.is_text();
369     let main_file = match input {
370         Input::File(ref file) => FileName::Real(file.clone()),
371         Input::Text(..) => FileName::Stdin,
372     };
373
374     // Parse the crate.
375     let codemap = Rc::new(CodeMap::new(FilePathMapping::empty()));
376     let mut parse_session = make_parse_sess(codemap.clone(), config);
377     let krate = match parse_input(input, &parse_session, config) {
378         Ok(krate) => krate,
379         Err(err) => {
380             match err {
381                 ParseError::Error(mut diagnostic) => diagnostic.emit(),
382                 ParseError::Panic => {
383                     // Note that if you see this message and want more information,
384                     // then go to `parse_input` and run the parse function without
385                     // `catch_unwind` so rustfmt panics and you can get a backtrace.
386                     should_emit_verbose(!input_is_stdin, config, || {
387                         println!("The Rust parser panicked")
388                     });
389                 }
390                 ParseError::Recovered => {}
391             }
392             summary.add_parsing_error();
393             return Err(ErrorKind::ParseError);
394         }
395     };
396     timer = timer.done_parsing();
397
398     // Suppress error output if we have to do any further parsing.
399     let silent_emitter = Box::new(EmitterWriter::new(
400         Box::new(Vec::new()),
401         Some(codemap.clone()),
402         false,
403         false,
404     ));
405     parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter);
406
407     let mut context = FormatContext::new(
408         &krate,
409         FormatReport::new(),
410         summary,
411         parse_session,
412         config,
413         handler,
414     );
415
416     let files = modules::list_files(&krate, context.parse_session.codemap())?;
417     for (path, module) in files {
418         if (config.skip_children() && path != main_file) || config.ignore().skip_file(&path) {
419             continue;
420         }
421         should_emit_verbose(!input_is_stdin, config, || println!("Formatting {}", path));
422         let is_root = path == main_file;
423         context.format_file(path, module, is_root)?;
424     }
425     timer = timer.done_formatting();
426
427     should_emit_verbose(!input_is_stdin, config, || {
428         println!(
429             "Spent {0:.3} secs in the parsing phase, and {1:.3} secs in the formatting phase",
430             timer.get_parse_time(),
431             timer.get_format_time(),
432         )
433     });
434
435     if context.report.has_warnings() {
436         context.summary.add_formatting_error();
437     }
438     {
439         let report_errs = &context.report.internal.borrow().1;
440         if report_errs.has_check_errors {
441             context.summary.add_check_error();
442         }
443         if report_errs.has_operational_errors {
444             context.summary.add_operational_error();
445         }
446     }
447
448     Ok((context.report, context.summary))
449 }
450
451 // Used for formatting files.
452 #[derive(new)]
453 struct FormatContext<'a, T: FormatHandler + 'a> {
454     krate: &'a ast::Crate,
455     report: FormatReport,
456     summary: Summary,
457     parse_session: ParseSess,
458     config: &'a Config,
459     handler: &'a mut T,
460 }
461
462 impl<'a, T: FormatHandler + 'a> FormatContext<'a, T> {
463     // Formats a single file/module.
464     fn format_file(
465         &mut self,
466         path: FileName,
467         module: &ast::Mod,
468         is_root: bool,
469     ) -> Result<(), ErrorKind> {
470         let filemap = self
471             .parse_session
472             .codemap()
473             .lookup_char_pos(module.inner.lo())
474             .file;
475         let big_snippet = filemap.src.as_ref().unwrap();
476         let snippet_provider = SnippetProvider::new(filemap.start_pos, big_snippet);
477         let mut visitor = FmtVisitor::from_codemap(
478             &self.parse_session,
479             &self.config,
480             &snippet_provider,
481             self.report.clone(),
482         );
483         // Format inner attributes if available.
484         if !self.krate.attrs.is_empty() && is_root {
485             visitor.skip_empty_lines(filemap.end_pos);
486             if visitor.visit_attrs(&self.krate.attrs, ast::AttrStyle::Inner) {
487                 visitor.push_rewrite(module.inner, None);
488             } else {
489                 visitor.format_separate_mod(module, &*filemap);
490             }
491         } else {
492             visitor.last_pos = filemap.start_pos;
493             visitor.skip_empty_lines(filemap.end_pos);
494             visitor.format_separate_mod(module, &*filemap);
495         };
496
497         debug_assert_eq!(
498             visitor.line_number,
499             ::utils::count_newlines(&visitor.buffer)
500         );
501
502         // For some reason, the codemap does not include terminating
503         // newlines so we must add one on for each file. This is sad.
504         filemap::append_newline(&mut visitor.buffer);
505
506         format_lines(
507             &mut visitor.buffer,
508             &path,
509             &visitor.skipped_range,
510             &self.config,
511             &self.report,
512         );
513         replace_with_system_newlines(&mut visitor.buffer, &self.config);
514
515         if visitor.macro_rewrite_failure {
516             self.summary.add_macro_format_failure();
517         }
518
519         self.handler.handle_formatted_file(path, visitor.buffer)
520     }
521 }
522
523 fn make_parse_sess(codemap: Rc<CodeMap>, config: &Config) -> ParseSess {
524     let tty_handler = if config.hide_parse_errors() {
525         let silent_emitter = Box::new(EmitterWriter::new(
526             Box::new(Vec::new()),
527             Some(codemap.clone()),
528             false,
529             false,
530         ));
531         Handler::with_emitter(true, false, silent_emitter)
532     } else {
533         let supports_color = term::stderr().map_or(false, |term| term.supports_color());
534         let color_cfg = if supports_color {
535             ColorConfig::Auto
536         } else {
537             ColorConfig::Never
538         };
539         Handler::with_tty_emitter(color_cfg, true, false, Some(codemap.clone()))
540     };
541
542     ParseSess::with_span_handler(tty_handler, codemap)
543 }
544
545 fn replace_with_system_newlines(text: &mut String, config: &Config) -> () {
546     let style = if config.newline_style() == NewlineStyle::Native {
547         if cfg!(windows) {
548             NewlineStyle::Windows
549         } else {
550             NewlineStyle::Unix
551         }
552     } else {
553         config.newline_style()
554     };
555
556     match style {
557         NewlineStyle::Unix => return,
558         NewlineStyle::Windows => {
559             let mut transformed = String::with_capacity(text.capacity());
560             for c in text.chars() {
561                 match c {
562                     '\n' => transformed.push_str("\r\n"),
563                     '\r' => continue,
564                     c => transformed.push(c),
565                 }
566             }
567             *text = transformed;
568         }
569         NewlineStyle::Native => unreachable!(),
570     }
571 }
572
573 /// A single span of changed lines, with 0 or more removed lines
574 /// and a vector of 0 or more inserted lines.
575 #[derive(Debug, PartialEq, Eq)]
576 pub(crate) struct ModifiedChunk {
577     /// The first to be removed from the original text
578     pub line_number_orig: u32,
579     /// The number of lines which have been replaced
580     pub lines_removed: u32,
581     /// The new lines
582     pub lines: Vec<String>,
583 }
584
585 /// Set of changed sections of a file.
586 #[derive(Debug, PartialEq, Eq)]
587 pub(crate) struct ModifiedLines {
588     /// The set of changed chunks.
589     pub chunks: Vec<ModifiedChunk>,
590 }
591
592 #[derive(Clone, Copy, Debug)]
593 enum Timer {
594     Initialized(Instant),
595     DoneParsing(Instant, Instant),
596     DoneFormatting(Instant, Instant, Instant),
597 }
598
599 impl Timer {
600     fn done_parsing(self) -> Self {
601         match self {
602             Timer::Initialized(init_time) => Timer::DoneParsing(init_time, Instant::now()),
603             _ => panic!("Timer can only transition to DoneParsing from Initialized state"),
604         }
605     }
606
607     fn done_formatting(self) -> Self {
608         match self {
609             Timer::DoneParsing(init_time, parse_time) => {
610                 Timer::DoneFormatting(init_time, parse_time, Instant::now())
611             }
612             _ => panic!("Timer can only transition to DoneFormatting from DoneParsing state"),
613         }
614     }
615
616     /// Returns the time it took to parse the source files in seconds.
617     fn get_parse_time(&self) -> f32 {
618         match *self {
619             Timer::DoneParsing(init, parse_time) | Timer::DoneFormatting(init, parse_time, _) => {
620                 // This should never underflow since `Instant::now()` guarantees monotonicity.
621                 Self::duration_to_f32(parse_time.duration_since(init))
622             }
623             Timer::Initialized(..) => unreachable!(),
624         }
625     }
626
627     /// Returns the time it took to go from the parsed AST to the formatted output. Parsing time is
628     /// not included.
629     fn get_format_time(&self) -> f32 {
630         match *self {
631             Timer::DoneFormatting(_init, parse_time, format_time) => {
632                 Self::duration_to_f32(format_time.duration_since(parse_time))
633             }
634             Timer::DoneParsing(..) | Timer::Initialized(..) => unreachable!(),
635         }
636     }
637
638     fn duration_to_f32(d: Duration) -> f32 {
639         d.as_secs() as f32 + d.subsec_nanos() as f32 / 1_000_000_000f32
640     }
641 }
642
643 /// A summary of a Rustfmt run.
644 #[derive(Debug, Default, Clone, Copy)]
645 pub struct Summary {
646     // Encountered e.g. an IO error.
647     has_operational_errors: bool,
648
649     // Failed to reformat code because of parsing errors.
650     has_parsing_errors: bool,
651
652     // Code is valid, but it is impossible to format it properly.
653     has_formatting_errors: bool,
654
655     // Code contains macro call that was unable to format.
656     pub(crate) has_macro_format_failure: bool,
657
658     // Failed a check, such as the license check or other opt-in checking.
659     has_check_errors: bool,
660
661     /// Formatted code differs from existing code (--check only).
662     pub has_diff: bool,
663 }
664
665 impl Summary {
666     pub fn has_operational_errors(&self) -> bool {
667         self.has_operational_errors
668     }
669
670     pub fn has_parsing_errors(&self) -> bool {
671         self.has_parsing_errors
672     }
673
674     pub fn has_formatting_errors(&self) -> bool {
675         self.has_formatting_errors
676     }
677
678     pub fn has_check_errors(&self) -> bool {
679         self.has_check_errors
680     }
681
682     pub(crate) fn has_macro_formatting_failure(&self) -> bool {
683         self.has_macro_format_failure
684     }
685
686     pub fn add_operational_error(&mut self) {
687         self.has_operational_errors = true;
688     }
689
690     pub(crate) fn add_parsing_error(&mut self) {
691         self.has_parsing_errors = true;
692     }
693
694     pub(crate) fn add_formatting_error(&mut self) {
695         self.has_formatting_errors = true;
696     }
697
698     pub(crate) fn add_check_error(&mut self) {
699         self.has_check_errors = true;
700     }
701
702     pub(crate) fn add_diff(&mut self) {
703         self.has_diff = true;
704     }
705
706     pub(crate) fn add_macro_format_failure(&mut self) {
707         self.has_macro_format_failure = true;
708     }
709
710     pub fn has_no_errors(&self) -> bool {
711         !(self.has_operational_errors
712             || self.has_parsing_errors
713             || self.has_formatting_errors
714             || self.has_diff)
715     }
716
717     /// Combine two summaries together.
718     pub fn add(&mut self, other: Summary) {
719         self.has_operational_errors |= other.has_operational_errors;
720         self.has_formatting_errors |= other.has_formatting_errors;
721         self.has_macro_format_failure |= other.has_macro_format_failure;
722         self.has_parsing_errors |= other.has_parsing_errors;
723         self.has_check_errors |= other.has_check_errors;
724         self.has_diff |= other.has_diff;
725     }
726 }