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