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::formatting
::generated
::is_generated_file
;
14 use crate::issues
::BadIssueSeeker
;
15 use crate::modules
::Module
;
16 use crate::parse
::parser
::{DirectoryOwnership, Parser, ParserError}
;
17 use crate::parse
::session
::ParseSess
;
18 use crate::utils
::{contains_skip, count_newlines}
;
19 use crate::visitor
::FmtVisitor
;
20 use crate::{modules, source_file, ErrorKind, FormatReport, Input, Session}
;
25 // A map of the files of a crate, with their new content
26 pub(crate) type SourceFile
= Vec
<FileRecord
>;
27 pub(crate) type FileRecord
= (FileName
, String
);
29 impl<'b
, T
: Write
+ 'b
> Session
<'b
, T
> {
30 pub(crate) fn format_input_inner(
34 ) -> Result
<FormatReport
, ErrorKind
> {
35 if !self.config
.version_meets_requirement() {
36 return Err(ErrorKind
::VersionMismatch
);
39 rustc_span
::create_session_if_not_set_then(self.config
.edition().into(), |_
| {
40 if self.config
.disable_all_formatting() {
41 // When the input is from stdin, echo back the input.
42 if let Input
::Text(ref buf
) = input
{
43 if let Err(e
) = io
::stdout().write_all(buf
.as_bytes()) {
44 return Err(From
::from(e
));
47 return Ok(FormatReport
::new());
50 let config
= &self.config
.clone();
51 let format_result
= format_project(input
, config
, self, is_macro_def
);
53 format_result
.map(|report
| {
54 self.errors
.add(&report
.internal
.borrow().1);
61 /// Determine if a module should be skipped. True if the module should be skipped, false otherwise.
62 fn should_skip_module
<T
: FormatHandler
>(
64 context
: &FormatContext
<'_
, T
>,
70 if contains_skip(module
.attrs()) {
74 if config
.skip_children() && path
!= main_file
{
78 if !input_is_stdin
&& context
.ignore_file(path
) {
82 // FIXME(calebcartwright) - we need to determine how we'll handle the
83 // `format_generated_files` option with stdin based input.
84 if !input_is_stdin
&& !config
.format_generated_files() {
85 let source_file
= context
.parse_session
.span_to_file_contents(module
.span
);
86 let src
= source_file
.src
.as_ref().expect("SourceFile without src");
88 if is_generated_file(src
) {
96 // Format an entire crate (or subset of the module tree).
97 fn format_project
<T
: FormatHandler
>(
102 ) -> Result
<FormatReport
, ErrorKind
> {
103 let mut timer
= Timer
::start();
105 let main_file
= input
.file_name();
106 let input_is_stdin
= main_file
== FileName
::Stdin
;
108 let parse_session
= ParseSess
::new(config
)?
;
109 if config
.skip_children() && parse_session
.ignore_file(&main_file
) {
110 return Ok(FormatReport
::new());
114 let mut report
= FormatReport
::new();
115 let directory_ownership
= input
.to_directory_ownership();
116 let krate
= match Parser
::parse_crate(input
, &parse_session
) {
118 // Surface parse error via Session (errors are merged there from report)
120 let forbid_verbose
= input_is_stdin
|| e
!= ParserError
::ParsePanicError
;
121 should_emit_verbose(forbid_verbose
, config
, || {
122 eprintln
!("The Rust parser panicked");
124 report
.add_parsing_error();
129 let mut context
= FormatContext
::new(&krate
, report
, parse_session
, config
, handler
);
130 let files
= modules
::ModResolver
::new(
131 &context
.parse_session
,
132 directory_ownership
.unwrap_or(DirectoryOwnership
::UnownedViaBlock
),
133 !input_is_stdin
&& !config
.skip_children(),
135 .visit_crate(&krate
)?
137 .filter(|(path
, module
)| {
138 !should_skip_module(config
, &context
, input_is_stdin
, &main_file
, path
, module
)
140 .collect
::<Vec
<_
>>();
142 timer
= timer
.done_parsing();
144 // Suppress error output if we have to do any further parsing.
145 context
.parse_session
.set_silent_emitter();
147 for (path
, module
) in files
{
148 should_emit_verbose(input_is_stdin
, config
, || println
!("Formatting {}", path
));
149 context
.format_file(path
, &module
, is_macro_def
)?
;
151 timer
= timer
.done_formatting();
153 should_emit_verbose(input_is_stdin
, config
, || {
155 "Spent {0:.3} secs in the parsing phase, and {1:.3} secs in the formatting phase",
156 timer
.get_parse_time(),
157 timer
.get_format_time(),
164 // Used for formatting files.
166 struct FormatContext
<'a
, T
: FormatHandler
> {
167 krate
: &'a ast
::Crate
,
168 report
: FormatReport
,
169 parse_session
: ParseSess
,
174 impl<'a
, T
: FormatHandler
+ 'a
> FormatContext
<'a
, T
> {
175 fn ignore_file(&self, path
: &FileName
) -> bool
{
176 self.parse_session
.ignore_file(path
)
179 // Formats a single file/module.
185 ) -> Result
<(), ErrorKind
> {
186 let snippet_provider
= self.parse_session
.snippet_provider(module
.span
);
187 let mut visitor
= FmtVisitor
::from_parse_sess(
193 visitor
.skip_context
.update_with_attrs(&self.krate
.attrs
);
194 visitor
.is_macro_def
= is_macro_def
;
195 visitor
.last_pos
= snippet_provider
.start_pos();
196 visitor
.skip_empty_lines(snippet_provider
.end_pos());
197 visitor
.format_separate_mod(module
, snippet_provider
.end_pos());
201 count_newlines(&visitor
.buffer
),
202 "failed in format_file visitor.buffer:\n {:?}",
206 // For some reason, the source_map does not include terminating
207 // newlines so we must add one on for each file. This is sad.
208 source_file
::append_newline(&mut visitor
.buffer
);
213 &visitor
.skipped_range
.borrow(),
219 self.config
.newline_style(),
221 snippet_provider
.entire_snippet(),
224 if visitor
.macro_rewrite_failure
{
225 self.report
.add_macro_format_failure();
228 .add_non_formatted_ranges(visitor
.skipped_range
.borrow().clone());
230 self.handler
.handle_formatted_file(
233 visitor
.buffer
.to_owned(),
239 // Handle the results of formatting.
240 trait FormatHandler
{
241 fn handle_formatted_file(
243 parse_session
: &ParseSess
,
246 report
: &mut FormatReport
,
247 ) -> Result
<(), ErrorKind
>;
250 impl<'b
, T
: Write
+ 'b
> FormatHandler
for Session
<'b
, T
> {
251 // Called for each formatted file.
252 fn handle_formatted_file(
254 parse_session
: &ParseSess
,
257 report
: &mut FormatReport
,
258 ) -> Result
<(), ErrorKind
> {
259 if let Some(ref mut out
) = self.out
{
260 match source_file
::write_file(
266 self.config
.newline_style(),
268 Ok(ref result
) if result
.has_diff
=> report
.add_diff(),
270 // Create a new error with path_str to help users see which files failed
271 let err_msg
= format
!("{}: {}", path
, e
);
272 return Err(io
::Error
::new(e
.kind(), err_msg
).into());
278 self.source_file
.push((path
, result
));
283 pub(crate) struct FormattingError
{
284 pub(crate) line
: usize,
285 pub(crate) kind
: ErrorKind
,
288 pub(crate) line_buffer
: String
,
291 impl FormattingError
{
292 pub(crate) fn from_span(
294 parse_sess
: &ParseSess
,
296 ) -> FormattingError
{
298 line
: parse_sess
.line_of_byte_pos(span
.lo()),
299 is_comment
: kind
.is_comment(),
302 line_buffer
: parse_sess
.span_to_first_line_string(span
),
306 pub(crate) fn is_internal(&self) -> bool
{
308 ErrorKind
::LineOverflow(..)
309 | ErrorKind
::TrailingWhitespace
310 | ErrorKind
::IoError(_
)
311 | ErrorKind
::ParseError
312 | ErrorKind
::LostComment
=> true,
317 pub(crate) fn msg_suffix(&self) -> &str {
318 if self.is_comment
|| self.is_string
{
319 "set `error_on_unformatted = false` to suppress \
320 the warning against comments or string literals\n"
327 pub(crate) fn format_len(&self) -> (usize, usize) {
329 ErrorKind
::LineOverflow(found
, max
) => (max
, found
- max
),
330 ErrorKind
::TrailingWhitespace
331 | ErrorKind
::DeprecatedAttr
332 | ErrorKind
::BadIssue(_
)
334 | ErrorKind
::LostComment
335 | ErrorKind
::LicenseCheck
=> {
336 let trailing_ws_start
= self
338 .rfind(|c
: char| !c
.is_whitespace())
343 self.line_buffer
.len() - trailing_ws_start
,
351 pub(crate) type FormatErrorMap
= HashMap
<FileName
, Vec
<FormattingError
>>;
353 #[derive(Default, Debug, PartialEq)]
354 pub(crate) struct ReportedErrors
{
355 // Encountered e.g., an IO error.
356 pub(crate) has_operational_errors
: bool
,
358 // Failed to reformat code because of parsing errors.
359 pub(crate) has_parsing_errors
: bool
,
361 // Code is valid, but it is impossible to format it properly.
362 pub(crate) has_formatting_errors
: bool
,
364 // Code contains macro call that was unable to format.
365 pub(crate) has_macro_format_failure
: bool
,
367 // Failed a check, such as the license check or other opt-in checking.
368 pub(crate) has_check_errors
: bool
,
370 /// Formatted code differs from existing code (--check only).
371 pub(crate) has_diff
: bool
,
373 /// Formatted code missed something, like lost comments or extra trailing space
374 pub(crate) has_unformatted_code_errors
: bool
,
377 impl ReportedErrors
{
378 /// Combine two summaries together.
379 pub(crate) fn add(&mut self, other
: &ReportedErrors
) {
380 self.has_operational_errors
|= other
.has_operational_errors
;
381 self.has_parsing_errors
|= other
.has_parsing_errors
;
382 self.has_formatting_errors
|= other
.has_formatting_errors
;
383 self.has_macro_format_failure
|= other
.has_macro_format_failure
;
384 self.has_check_errors
|= other
.has_check_errors
;
385 self.has_diff
|= other
.has_diff
;
386 self.has_unformatted_code_errors
|= other
.has_unformatted_code_errors
;
390 #[derive(Clone, Copy, Debug)]
393 Initialized(Instant
),
394 DoneParsing(Instant
, Instant
),
395 DoneFormatting(Instant
, Instant
, Instant
),
399 fn start() -> Timer
{
400 if cfg
!(target_arch
= "wasm32") {
403 Timer
::Initialized(Instant
::now())
406 fn done_parsing(self) -> Self {
408 Timer
::Disabled
=> Timer
::Disabled
,
409 Timer
::Initialized(init_time
) => Timer
::DoneParsing(init_time
, Instant
::now()),
410 _
=> panic
!("Timer can only transition to DoneParsing from Initialized state"),
414 fn done_formatting(self) -> Self {
416 Timer
::Disabled
=> Timer
::Disabled
,
417 Timer
::DoneParsing(init_time
, parse_time
) => {
418 Timer
::DoneFormatting(init_time
, parse_time
, Instant
::now())
420 _
=> panic
!("Timer can only transition to DoneFormatting from DoneParsing state"),
424 /// Returns the time it took to parse the source files in seconds.
425 fn get_parse_time(&self) -> f32 {
427 Timer
::Disabled
=> panic
!("this platform cannot time execution"),
428 Timer
::DoneParsing(init
, parse_time
) | Timer
::DoneFormatting(init
, parse_time
, _
) => {
429 // This should never underflow since `Instant::now()` guarantees monotonicity.
430 Self::duration_to_f32(parse_time
.duration_since(init
))
432 Timer
::Initialized(..) => unreachable
!(),
436 /// Returns the time it took to go from the parsed AST to the formatted output. Parsing time is
438 fn get_format_time(&self) -> f32 {
440 Timer
::Disabled
=> panic
!("this platform cannot time execution"),
441 Timer
::DoneFormatting(_init
, parse_time
, format_time
) => {
442 Self::duration_to_f32(format_time
.duration_since(parse_time
))
444 Timer
::DoneParsing(..) | Timer
::Initialized(..) => unreachable
!(),
448 fn duration_to_f32(d
: Duration
) -> f32 {
449 d
.as_secs() as f32 + d
.subsec_nanos() as f32 / 1_000_000_000f32
453 // Formatting done on a char by char or line by line basis.
454 // FIXME(#20): other stuff for parity with make tidy.
458 skipped_range
: &[(usize, usize)],
460 report
: &FormatReport
,
462 let mut formatter
= FormatLines
::new(name
, skipped_range
, config
);
463 formatter
.check_license(text
);
464 formatter
.iterate(text
);
466 if formatter
.newline_count
> 1 {
467 debug
!("track truncate: {} {}", text
.len(), formatter
.newline_count
);
468 let line
= text
.len() - formatter
.newline_count
+ 1;
472 report
.append(name
.clone(), formatter
.errors
);
475 struct FormatLines
<'a
> {
477 skipped_range
: &'a
[(usize, usize)],
478 last_was_space
: bool
,
481 newline_count
: usize,
482 errors
: Vec
<FormattingError
>,
483 issue_seeker
: BadIssueSeeker
,
485 current_line_contains_string_literal
: bool
,
487 allow_issue_seek
: bool
,
491 impl<'a
> FormatLines
<'a
> {
494 skipped_range
: &'a
[(usize, usize)],
496 ) -> FormatLines
<'a
> {
497 let issue_seeker
= BadIssueSeeker
::new(config
.report_todo(), config
.report_fixme());
501 last_was_space
: false,
506 allow_issue_seek
: !issue_seeker
.is_disabled(),
508 line_buffer
: String
::with_capacity(config
.max_width() * 2),
509 current_line_contains_string_literal
: false,
510 format_line
: config
.file_lines().contains_line(name
, 1),
515 fn check_license(&mut self, text
: &mut String
) {
516 if let Some(ref license_template
) = self.config
.license_template
{
517 if !license_template
.is_match(text
) {
518 self.errors
.push(FormattingError
{
520 kind
: ErrorKind
::LicenseCheck
,
523 line_buffer
: String
::new(),
529 // Iterate over the chars in the file map.
530 fn iterate(&mut self, text
: &mut String
) {
531 for (kind
, c
) in CharClasses
::new(text
.chars()) {
536 if self.allow_issue_seek
&& self.format_line
{
537 // Add warnings for bad todos/ fixmes
538 if let Some(issue
) = self.issue_seeker
.inspect(c
) {
539 self.push_err(ErrorKind
::BadIssue(issue
), false, false);
551 fn new_line(&mut self, kind
: FullCodeCharKind
) {
552 if self.format_line
{
553 // Check for (and record) trailing whitespace.
554 if self.last_was_space
{
555 if self.should_report_error(kind
, &ErrorKind
::TrailingWhitespace
)
556 && !self.is_skipped_line()
559 ErrorKind
::TrailingWhitespace
,
567 // Check for any line width errors we couldn't correct.
568 let error_kind
= ErrorKind
::LineOverflow(self.line_len
, self.config
.max_width());
569 if self.line_len
> self.config
.max_width()
570 && !self.is_skipped_line()
571 && self.should_report_error(kind
, &error_kind
)
573 let is_string
= self.current_line_contains_string_literal
;
574 self.push_err(error_kind
, kind
.is_comment(), is_string
);
580 self.format_line
= self
583 .contains_line(self.name
, self.cur_line
);
584 self.newline_count
+= 1;
585 self.last_was_space
= false;
586 self.line_buffer
.clear();
587 self.current_line_contains_string_literal
= false;
590 fn char(&mut self, c
: char, kind
: FullCodeCharKind
) {
591 self.newline_count
= 0;
592 self.line_len
+= if c
== '
\t'
{
593 self.config
.tab_spaces()
597 self.last_was_space
= c
.is_whitespace();
598 self.line_buffer
.push(c
);
599 if kind
.is_string() {
600 self.current_line_contains_string_literal
= true;
604 fn push_err(&mut self, kind
: ErrorKind
, is_comment
: bool
, is_string
: bool
) {
605 self.errors
.push(FormattingError
{
610 line_buffer
: self.line_buffer
.clone(),
614 fn should_report_error(&self, char_kind
: FullCodeCharKind
, error_kind
: &ErrorKind
) -> bool
{
615 let allow_error_report
= if char_kind
.is_comment()
616 || self.current_line_contains_string_literal
617 || error_kind
.is_comment()
619 self.config
.error_on_unformatted()
625 ErrorKind
::LineOverflow(..) => {
626 self.config
.error_on_line_overflow() && allow_error_report
628 ErrorKind
::TrailingWhitespace
| ErrorKind
::LostComment
=> allow_error_report
,
633 /// Returns `true` if the line with the given line number was skipped by `#[rustfmt::skip]`.
634 fn is_skipped_line(&self) -> bool
{
637 .any(|&(lo
, hi
)| lo
<= self.cur_line
&& self.cur_line
<= hi
)
641 fn should_emit_verbose
<F
>(forbid_verbose_output
: bool
, config
: &Config
, f
: F
)
645 if config
.verbose() == Verbosity
::Verbose
&& !forbid_verbose_output
{