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(config
, input
, directory_ownership
, &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
::UnownedViaMod
),
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
.as_ref().inner
);
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
,
336 impl ReportedErrors
{
337 /// Combine two summaries together.
338 pub(crate) fn add(&mut self, other
: &ReportedErrors
) {
339 self.has_operational_errors
|= other
.has_operational_errors
;
340 self.has_parsing_errors
|= other
.has_parsing_errors
;
341 self.has_formatting_errors
|= other
.has_formatting_errors
;
342 self.has_macro_format_failure
|= other
.has_macro_format_failure
;
343 self.has_check_errors
|= other
.has_check_errors
;
344 self.has_diff
|= other
.has_diff
;
348 #[derive(Clone, Copy, Debug)]
351 Initialized(Instant
),
352 DoneParsing(Instant
, Instant
),
353 DoneFormatting(Instant
, Instant
, Instant
),
357 fn start() -> Timer
{
358 if cfg
!(target_arch
= "wasm32") {
361 Timer
::Initialized(Instant
::now())
364 fn done_parsing(self) -> Self {
366 Timer
::Disabled
=> Timer
::Disabled
,
367 Timer
::Initialized(init_time
) => Timer
::DoneParsing(init_time
, Instant
::now()),
368 _
=> panic
!("Timer can only transition to DoneParsing from Initialized state"),
372 fn done_formatting(self) -> Self {
374 Timer
::Disabled
=> Timer
::Disabled
,
375 Timer
::DoneParsing(init_time
, parse_time
) => {
376 Timer
::DoneFormatting(init_time
, parse_time
, Instant
::now())
378 _
=> panic
!("Timer can only transition to DoneFormatting from DoneParsing state"),
382 /// Returns the time it took to parse the source files in seconds.
383 fn get_parse_time(&self) -> f32 {
385 Timer
::Disabled
=> panic
!("this platform cannot time execution"),
386 Timer
::DoneParsing(init
, parse_time
) | Timer
::DoneFormatting(init
, parse_time
, _
) => {
387 // This should never underflow since `Instant::now()` guarantees monotonicity.
388 Self::duration_to_f32(parse_time
.duration_since(init
))
390 Timer
::Initialized(..) => unreachable
!(),
394 /// Returns the time it took to go from the parsed AST to the formatted output. Parsing time is
396 fn get_format_time(&self) -> f32 {
398 Timer
::Disabled
=> panic
!("this platform cannot time execution"),
399 Timer
::DoneFormatting(_init
, parse_time
, format_time
) => {
400 Self::duration_to_f32(format_time
.duration_since(parse_time
))
402 Timer
::DoneParsing(..) | Timer
::Initialized(..) => unreachable
!(),
406 fn duration_to_f32(d
: Duration
) -> f32 {
407 d
.as_secs() as f32 + d
.subsec_nanos() as f32 / 1_000_000_000f32
411 // Formatting done on a char by char or line by line basis.
412 // FIXME(#20): other stuff for parity with make tidy.
416 skipped_range
: &[(usize, usize)],
418 report
: &FormatReport
,
420 let mut formatter
= FormatLines
::new(name
, skipped_range
, config
);
421 formatter
.check_license(text
);
422 formatter
.iterate(text
);
424 if formatter
.newline_count
> 1 {
425 debug
!("track truncate: {} {}", text
.len(), formatter
.newline_count
);
426 let line
= text
.len() - formatter
.newline_count
+ 1;
430 report
.append(name
.clone(), formatter
.errors
);
433 struct FormatLines
<'a
> {
435 skipped_range
: &'a
[(usize, usize)],
436 last_was_space
: bool
,
439 newline_count
: usize,
440 errors
: Vec
<FormattingError
>,
441 issue_seeker
: BadIssueSeeker
,
443 current_line_contains_string_literal
: bool
,
445 allow_issue_seek
: bool
,
449 impl<'a
> FormatLines
<'a
> {
452 skipped_range
: &'a
[(usize, usize)],
454 ) -> FormatLines
<'a
> {
455 let issue_seeker
= BadIssueSeeker
::new(config
.report_todo(), config
.report_fixme());
459 last_was_space
: false,
464 allow_issue_seek
: !issue_seeker
.is_disabled(),
466 line_buffer
: String
::with_capacity(config
.max_width() * 2),
467 current_line_contains_string_literal
: false,
468 format_line
: config
.file_lines().contains_line(name
, 1),
473 fn check_license(&mut self, text
: &mut String
) {
474 if let Some(ref license_template
) = self.config
.license_template
{
475 if !license_template
.is_match(text
) {
476 self.errors
.push(FormattingError
{
478 kind
: ErrorKind
::LicenseCheck
,
481 line_buffer
: String
::new(),
487 // Iterate over the chars in the file map.
488 fn iterate(&mut self, text
: &mut String
) {
489 for (kind
, c
) in CharClasses
::new(text
.chars()) {
494 if self.allow_issue_seek
&& self.format_line
{
495 // Add warnings for bad todos/ fixmes
496 if let Some(issue
) = self.issue_seeker
.inspect(c
) {
497 self.push_err(ErrorKind
::BadIssue(issue
), false, false);
509 fn new_line(&mut self, kind
: FullCodeCharKind
) {
510 if self.format_line
{
511 // Check for (and record) trailing whitespace.
512 if self.last_was_space
{
513 if self.should_report_error(kind
, &ErrorKind
::TrailingWhitespace
)
514 && !self.is_skipped_line()
517 ErrorKind
::TrailingWhitespace
,
525 // Check for any line width errors we couldn't correct.
526 let error_kind
= ErrorKind
::LineOverflow(self.line_len
, self.config
.max_width());
527 if self.line_len
> self.config
.max_width()
528 && !self.is_skipped_line()
529 && self.should_report_error(kind
, &error_kind
)
531 let is_string
= self.current_line_contains_string_literal
;
532 self.push_err(error_kind
, kind
.is_comment(), is_string
);
538 self.format_line
= self
541 .contains_line(self.name
, self.cur_line
);
542 self.newline_count
+= 1;
543 self.last_was_space
= false;
544 self.line_buffer
.clear();
545 self.current_line_contains_string_literal
= false;
548 fn char(&mut self, c
: char, kind
: FullCodeCharKind
) {
549 self.newline_count
= 0;
550 self.line_len
+= if c
== '
\t'
{
551 self.config
.tab_spaces()
555 self.last_was_space
= c
.is_whitespace();
556 self.line_buffer
.push(c
);
557 if kind
.is_string() {
558 self.current_line_contains_string_literal
= true;
562 fn push_err(&mut self, kind
: ErrorKind
, is_comment
: bool
, is_string
: bool
) {
563 self.errors
.push(FormattingError
{
568 line_buffer
: self.line_buffer
.clone(),
572 fn should_report_error(&self, char_kind
: FullCodeCharKind
, error_kind
: &ErrorKind
) -> bool
{
573 let allow_error_report
= if char_kind
.is_comment()
574 || self.current_line_contains_string_literal
575 || error_kind
.is_comment()
577 self.config
.error_on_unformatted()
583 ErrorKind
::LineOverflow(..) => {
584 self.config
.error_on_line_overflow() && allow_error_report
586 ErrorKind
::TrailingWhitespace
| ErrorKind
::LostComment
=> allow_error_report
,
591 /// Returns `true` if the line with the given line number was skipped by `#[rustfmt::skip]`.
592 fn is_skipped_line(&self) -> bool
{
595 .any(|&(lo
, hi
)| lo
<= self.cur_line
&& self.cur_line
<= hi
)
599 fn should_emit_verbose
<F
>(forbid_verbose_output
: bool
, config
: &Config
, f
: F
)
603 if config
.verbose() == Verbosity
::Verbose
&& !forbid_verbose_output
{