1 // High level formatting functions.
3 use std
::collections
::HashMap
;
4 use std
::io
::{self, Write}
;
5 use std
::time
::{Duration, Instant}
;
10 use self::newline_style
::apply_newline_style
;
11 use crate::comment
::{CharClasses, FullCodeCharKind}
;
12 use crate::config
::{Config, FileName, Verbosity}
;
13 use crate::issues
::BadIssueSeeker
;
14 use crate::modules
::Module
;
15 use crate::syntux
::parser
::{DirectoryOwnership, Parser, ParserError}
;
16 use crate::syntux
::session
::ParseSess
;
17 use crate::utils
::count_newlines
;
18 use crate::visitor
::FmtVisitor
;
19 use crate::{modules, source_file, ErrorKind, FormatReport, Input, Session}
;
23 // A map of the files of a crate, with their new content
24 pub(crate) type SourceFile
= Vec
<FileRecord
>;
25 pub(crate) type FileRecord
= (FileName
, String
);
27 impl<'b
, T
: Write
+ 'b
> Session
<'b
, T
> {
28 pub(crate) fn format_input_inner(
32 ) -> Result
<FormatReport
, ErrorKind
> {
33 if !self.config
.version_meets_requirement() {
34 return Err(ErrorKind
::VersionMismatch
);
37 rustc_span
::with_session_globals(self.config
.edition().into(), || {
38 if self.config
.disable_all_formatting() {
39 // When the input is from stdin, echo back the input.
40 if let Input
::Text(ref buf
) = input
{
41 if let Err(e
) = io
::stdout().write_all(buf
.as_bytes()) {
42 return Err(From
::from(e
));
45 return Ok(FormatReport
::new());
48 let config
= &self.config
.clone();
49 let format_result
= format_project(input
, config
, self, is_macro_def
);
51 format_result
.map(|report
| {
52 self.errors
.add(&report
.internal
.borrow().1);
59 // Format an entire crate (or subset of the module tree).
60 fn format_project
<T
: FormatHandler
>(
65 ) -> Result
<FormatReport
, ErrorKind
> {
66 let mut timer
= Timer
::start();
68 let main_file
= input
.file_name();
69 let input_is_stdin
= main_file
== FileName
::Stdin
;
71 let parse_session
= ParseSess
::new(config
)?
;
72 if config
.skip_children() && parse_session
.ignore_file(&main_file
) {
73 return Ok(FormatReport
::new());
77 let mut report
= FormatReport
::new();
78 let directory_ownership
= input
.to_directory_ownership();
79 let krate
= match Parser
::parse_crate(input
, &parse_session
) {
81 // Surface parse error via Session (errors are merged there from report)
83 let forbid_verbose
= input_is_stdin
|| e
!= ParserError
::ParsePanicError
;
84 should_emit_verbose(forbid_verbose
, config
, || {
85 eprintln
!("The Rust parser panicked");
87 report
.add_parsing_error();
92 let mut context
= FormatContext
::new(&krate
, report
, parse_session
, config
, handler
);
93 let files
= modules
::ModResolver
::new(
94 &context
.parse_session
,
95 directory_ownership
.unwrap_or(DirectoryOwnership
::UnownedViaBlock
),
96 !input_is_stdin
&& !config
.skip_children(),
98 .visit_crate(&krate
)?
;
100 timer
= timer
.done_parsing();
102 // Suppress error output if we have to do any further parsing.
103 context
.parse_session
.set_silent_emitter();
105 for (path
, module
) in files
{
106 let should_ignore
= !input_is_stdin
&& context
.ignore_file(&path
);
107 if (config
.skip_children() && path
!= main_file
) || should_ignore
{
110 should_emit_verbose(input_is_stdin
, config
, || println
!("Formatting {}", path
));
111 context
.format_file(path
, &module
, is_macro_def
)?
;
113 timer
= timer
.done_formatting();
115 should_emit_verbose(input_is_stdin
, config
, || {
117 "Spent {0:.3} secs in the parsing phase, and {1:.3} secs in the formatting phase",
118 timer
.get_parse_time(),
119 timer
.get_format_time(),
126 // Used for formatting files.
128 struct FormatContext
<'a
, T
: FormatHandler
> {
129 krate
: &'a ast
::Crate
,
130 report
: FormatReport
,
131 parse_session
: ParseSess
,
136 impl<'a
, T
: FormatHandler
+ 'a
> FormatContext
<'a
, T
> {
137 fn ignore_file(&self, path
: &FileName
) -> bool
{
138 self.parse_session
.ignore_file(path
)
141 // Formats a single file/module.
147 ) -> Result
<(), ErrorKind
> {
148 let snippet_provider
= self.parse_session
.snippet_provider(module
.span
);
149 let mut visitor
= FmtVisitor
::from_parse_sess(
155 visitor
.skip_context
.update_with_attrs(&self.krate
.attrs
);
156 visitor
.is_macro_def
= is_macro_def
;
157 visitor
.last_pos
= snippet_provider
.start_pos();
158 visitor
.skip_empty_lines(snippet_provider
.end_pos());
159 visitor
.format_separate_mod(module
, snippet_provider
.end_pos());
163 count_newlines(&visitor
.buffer
),
164 "failed in format_file visitor.buffer:\n {:?}",
168 // For some reason, the source_map does not include terminating
169 // newlines so we must add one on for each file. This is sad.
170 source_file
::append_newline(&mut visitor
.buffer
);
175 &visitor
.skipped_range
.borrow(),
181 self.config
.newline_style(),
183 snippet_provider
.entire_snippet(),
186 if visitor
.macro_rewrite_failure
{
187 self.report
.add_macro_format_failure();
190 .add_non_formatted_ranges(visitor
.skipped_range
.borrow().clone());
192 self.handler
.handle_formatted_file(
195 visitor
.buffer
.to_owned(),
201 // Handle the results of formatting.
202 trait FormatHandler
{
203 fn handle_formatted_file(
205 parse_session
: &ParseSess
,
208 report
: &mut FormatReport
,
209 ) -> Result
<(), ErrorKind
>;
212 impl<'b
, T
: Write
+ 'b
> FormatHandler
for Session
<'b
, T
> {
213 // Called for each formatted file.
214 fn handle_formatted_file(
216 parse_session
: &ParseSess
,
219 report
: &mut FormatReport
,
220 ) -> Result
<(), ErrorKind
> {
221 if let Some(ref mut out
) = self.out
{
222 match source_file
::write_file(
228 self.config
.newline_style(),
230 Ok(ref result
) if result
.has_diff
=> report
.add_diff(),
232 // Create a new error with path_str to help users see which files failed
233 let err_msg
= format
!("{}: {}", path
, e
);
234 return Err(io
::Error
::new(e
.kind(), err_msg
).into());
240 self.source_file
.push((path
, result
));
245 pub(crate) struct FormattingError
{
246 pub(crate) line
: usize,
247 pub(crate) kind
: ErrorKind
,
250 pub(crate) line_buffer
: String
,
253 impl FormattingError
{
254 pub(crate) fn from_span(
256 parse_sess
: &ParseSess
,
258 ) -> FormattingError
{
260 line
: parse_sess
.line_of_byte_pos(span
.lo()),
261 is_comment
: kind
.is_comment(),
264 line_buffer
: parse_sess
.span_to_first_line_string(span
),
268 pub(crate) fn is_internal(&self) -> bool
{
270 ErrorKind
::LineOverflow(..)
271 | ErrorKind
::TrailingWhitespace
272 | ErrorKind
::IoError(_
)
273 | ErrorKind
::ParseError
274 | ErrorKind
::LostComment
=> true,
279 pub(crate) fn msg_suffix(&self) -> &str {
280 if self.is_comment
|| self.is_string
{
281 "set `error_on_unformatted = false` to suppress \
282 the warning against comments or string literals\n"
289 pub(crate) fn format_len(&self) -> (usize, usize) {
291 ErrorKind
::LineOverflow(found
, max
) => (max
, found
- max
),
292 ErrorKind
::TrailingWhitespace
293 | ErrorKind
::DeprecatedAttr
294 | ErrorKind
::BadIssue(_
)
296 | ErrorKind
::LostComment
297 | ErrorKind
::LicenseCheck
=> {
298 let trailing_ws_start
= self
300 .rfind(|c
: char| !c
.is_whitespace())
305 self.line_buffer
.len() - trailing_ws_start
,
313 pub(crate) type FormatErrorMap
= HashMap
<FileName
, Vec
<FormattingError
>>;
315 #[derive(Default, Debug, PartialEq)]
316 pub(crate) struct ReportedErrors
{
317 // Encountered e.g., an IO error.
318 pub(crate) has_operational_errors
: bool
,
320 // Failed to reformat code because of parsing errors.
321 pub(crate) has_parsing_errors
: bool
,
323 // Code is valid, but it is impossible to format it properly.
324 pub(crate) has_formatting_errors
: bool
,
326 // Code contains macro call that was unable to format.
327 pub(crate) has_macro_format_failure
: bool
,
329 // Failed a check, such as the license check or other opt-in checking.
330 pub(crate) has_check_errors
: bool
,
332 /// Formatted code differs from existing code (--check only).
333 pub(crate) has_diff
: bool
,
335 /// Formatted code missed something, like lost comments or extra trailing space
336 pub(crate) has_unformatted_code_errors
: bool
,
339 impl ReportedErrors
{
340 /// Combine two summaries together.
341 pub(crate) fn add(&mut self, other
: &ReportedErrors
) {
342 self.has_operational_errors
|= other
.has_operational_errors
;
343 self.has_parsing_errors
|= other
.has_parsing_errors
;
344 self.has_formatting_errors
|= other
.has_formatting_errors
;
345 self.has_macro_format_failure
|= other
.has_macro_format_failure
;
346 self.has_check_errors
|= other
.has_check_errors
;
347 self.has_diff
|= other
.has_diff
;
348 self.has_unformatted_code_errors
|= other
.has_unformatted_code_errors
;
352 #[derive(Clone, Copy, Debug)]
355 Initialized(Instant
),
356 DoneParsing(Instant
, Instant
),
357 DoneFormatting(Instant
, Instant
, Instant
),
361 fn start() -> Timer
{
362 if cfg
!(target_arch
= "wasm32") {
365 Timer
::Initialized(Instant
::now())
368 fn done_parsing(self) -> Self {
370 Timer
::Disabled
=> Timer
::Disabled
,
371 Timer
::Initialized(init_time
) => Timer
::DoneParsing(init_time
, Instant
::now()),
372 _
=> panic
!("Timer can only transition to DoneParsing from Initialized state"),
376 fn done_formatting(self) -> Self {
378 Timer
::Disabled
=> Timer
::Disabled
,
379 Timer
::DoneParsing(init_time
, parse_time
) => {
380 Timer
::DoneFormatting(init_time
, parse_time
, Instant
::now())
382 _
=> panic
!("Timer can only transition to DoneFormatting from DoneParsing state"),
386 /// Returns the time it took to parse the source files in seconds.
387 fn get_parse_time(&self) -> f32 {
389 Timer
::Disabled
=> panic
!("this platform cannot time execution"),
390 Timer
::DoneParsing(init
, parse_time
) | Timer
::DoneFormatting(init
, parse_time
, _
) => {
391 // This should never underflow since `Instant::now()` guarantees monotonicity.
392 Self::duration_to_f32(parse_time
.duration_since(init
))
394 Timer
::Initialized(..) => unreachable
!(),
398 /// Returns the time it took to go from the parsed AST to the formatted output. Parsing time is
400 fn get_format_time(&self) -> f32 {
402 Timer
::Disabled
=> panic
!("this platform cannot time execution"),
403 Timer
::DoneFormatting(_init
, parse_time
, format_time
) => {
404 Self::duration_to_f32(format_time
.duration_since(parse_time
))
406 Timer
::DoneParsing(..) | Timer
::Initialized(..) => unreachable
!(),
410 fn duration_to_f32(d
: Duration
) -> f32 {
411 d
.as_secs() as f32 + d
.subsec_nanos() as f32 / 1_000_000_000f32
415 // Formatting done on a char by char or line by line basis.
416 // FIXME(#20): other stuff for parity with make tidy.
420 skipped_range
: &[(usize, usize)],
422 report
: &FormatReport
,
424 let mut formatter
= FormatLines
::new(name
, skipped_range
, config
);
425 formatter
.check_license(text
);
426 formatter
.iterate(text
);
428 if formatter
.newline_count
> 1 {
429 debug
!("track truncate: {} {}", text
.len(), formatter
.newline_count
);
430 let line
= text
.len() - formatter
.newline_count
+ 1;
434 report
.append(name
.clone(), formatter
.errors
);
437 struct FormatLines
<'a
> {
439 skipped_range
: &'a
[(usize, usize)],
440 last_was_space
: bool
,
443 newline_count
: usize,
444 errors
: Vec
<FormattingError
>,
445 issue_seeker
: BadIssueSeeker
,
447 current_line_contains_string_literal
: bool
,
449 allow_issue_seek
: bool
,
453 impl<'a
> FormatLines
<'a
> {
456 skipped_range
: &'a
[(usize, usize)],
458 ) -> FormatLines
<'a
> {
459 let issue_seeker
= BadIssueSeeker
::new(config
.report_todo(), config
.report_fixme());
463 last_was_space
: false,
468 allow_issue_seek
: !issue_seeker
.is_disabled(),
470 line_buffer
: String
::with_capacity(config
.max_width() * 2),
471 current_line_contains_string_literal
: false,
472 format_line
: config
.file_lines().contains_line(name
, 1),
477 fn check_license(&mut self, text
: &mut String
) {
478 if let Some(ref license_template
) = self.config
.license_template
{
479 if !license_template
.is_match(text
) {
480 self.errors
.push(FormattingError
{
482 kind
: ErrorKind
::LicenseCheck
,
485 line_buffer
: String
::new(),
491 // Iterate over the chars in the file map.
492 fn iterate(&mut self, text
: &mut String
) {
493 for (kind
, c
) in CharClasses
::new(text
.chars()) {
498 if self.allow_issue_seek
&& self.format_line
{
499 // Add warnings for bad todos/ fixmes
500 if let Some(issue
) = self.issue_seeker
.inspect(c
) {
501 self.push_err(ErrorKind
::BadIssue(issue
), false, false);
513 fn new_line(&mut self, kind
: FullCodeCharKind
) {
514 if self.format_line
{
515 // Check for (and record) trailing whitespace.
516 if self.last_was_space
{
517 if self.should_report_error(kind
, &ErrorKind
::TrailingWhitespace
)
518 && !self.is_skipped_line()
521 ErrorKind
::TrailingWhitespace
,
529 // Check for any line width errors we couldn't correct.
530 let error_kind
= ErrorKind
::LineOverflow(self.line_len
, self.config
.max_width());
531 if self.line_len
> self.config
.max_width()
532 && !self.is_skipped_line()
533 && self.should_report_error(kind
, &error_kind
)
535 let is_string
= self.current_line_contains_string_literal
;
536 self.push_err(error_kind
, kind
.is_comment(), is_string
);
542 self.format_line
= self
545 .contains_line(self.name
, self.cur_line
);
546 self.newline_count
+= 1;
547 self.last_was_space
= false;
548 self.line_buffer
.clear();
549 self.current_line_contains_string_literal
= false;
552 fn char(&mut self, c
: char, kind
: FullCodeCharKind
) {
553 self.newline_count
= 0;
554 self.line_len
+= if c
== '
\t'
{
555 self.config
.tab_spaces()
559 self.last_was_space
= c
.is_whitespace();
560 self.line_buffer
.push(c
);
561 if kind
.is_string() {
562 self.current_line_contains_string_literal
= true;
566 fn push_err(&mut self, kind
: ErrorKind
, is_comment
: bool
, is_string
: bool
) {
567 self.errors
.push(FormattingError
{
572 line_buffer
: self.line_buffer
.clone(),
576 fn should_report_error(&self, char_kind
: FullCodeCharKind
, error_kind
: &ErrorKind
) -> bool
{
577 let allow_error_report
= if char_kind
.is_comment()
578 || self.current_line_contains_string_literal
579 || error_kind
.is_comment()
581 self.config
.error_on_unformatted()
587 ErrorKind
::LineOverflow(..) => {
588 self.config
.error_on_line_overflow() && allow_error_report
590 ErrorKind
::TrailingWhitespace
| ErrorKind
::LostComment
=> allow_error_report
,
595 /// Returns `true` if the line with the given line number was skipped by `#[rustfmt::skip]`.
596 fn is_skipped_line(&self) -> bool
{
599 .any(|&(lo
, hi
)| lo
<= self.cur_line
&& self.cur_line
<= hi
)
603 fn should_emit_verbose
<F
>(forbid_verbose_output
: bool
, config
: &Config
, f
: F
)
607 if config
.verbose() == Verbosity
::Verbose
&& !forbid_verbose_output
{