1use std::collections::HashMap;
4use std::io::{self, Write};
5use std::ops::Range;
6use std::time::{Duration, Instant};
7
8use rustc_ast::ast;
9use rustc_span::Span;
10use tracing::debug;
11
12use self::newline_style::apply_newline_style;
13use crate::comment::{CharClasses, FullCodeCharKind};
14use crate::config::{Config, FileName, Verbosity};
15use crate::formatting::generated::is_generated_file;
16use crate::modules::Module;
17use crate::parse::parser::{DirectoryOwnership, Parser, ParserError};
18use crate::parse::session::ParseSess;
19use crate::utils::{contains_skip, count_newlines};
20use crate::visitor::FmtVisitor;
21use crate::{ErrorKind, FormatReport, Input, Session, modules, source_file};
22
23mod generated;
24mod newline_style;
25
26pub(crate) type SourceFile = Vec<FileRecord>;
28pub(crate) type FileRecord = (FileName, String);
29
30impl<'b, T: Write + 'b> Session<'b, T> {
31 pub(crate) fn format_input_inner(
32 &mut self,
33 input: Input,
34 is_macro_def: bool,
35 ) -> Result<FormatReport, ErrorKind> {
36 if !self.config.version_meets_requirement() {
37 return Err(ErrorKind::VersionMismatch);
38 }
39
40 rustc_span::create_session_if_not_set_then(self.config.edition().into(), |_| {
41 if self.config.disable_all_formatting() {
42 return match input {
44 Input::Text(ref buf) => echo_back_stdin(buf),
45 _ => Ok(FormatReport::new()),
46 };
47 }
48
49 let config = &self.config.clone();
50 let format_result = format_project(input, config, self, is_macro_def);
51
52 format_result.map(|report| {
53 self.errors.add(&report.internal.borrow().1);
54 report
55 })
56 })
57 }
58}
59
60fn should_skip_module<T: FormatHandler>(
62 config: &Config,
63 context: &FormatContext<'_, T>,
64 input_is_stdin: bool,
65 main_file: &FileName,
66 path: &FileName,
67 module: &Module<'_>,
68) -> bool {
69 if contains_skip(module.attrs()) {
70 return true;
71 }
72
73 if config.skip_children() && path != main_file {
74 return true;
75 }
76
77 if !input_is_stdin && context.ignore_file(path) {
78 return true;
79 }
80
81 if !input_is_stdin && !config.format_generated_files() {
84 let source_file = context.psess.span_to_file_contents(module.span);
85 let src = source_file.src.as_ref().expect("SourceFile without src");
86
87 if is_generated_file(src, config) {
88 return true;
89 }
90 }
91
92 false
93}
94
95fn echo_back_stdin(input: &str) -> Result<FormatReport, ErrorKind> {
96 if let Err(e) = io::stdout().write_all(input.as_bytes()) {
97 return Err(From::from(e));
98 }
99 Ok(FormatReport::new())
100}
101
102fn format_project<T: FormatHandler>(
104 input: Input,
105 config: &Config,
106 handler: &mut T,
107 is_macro_def: bool,
108) -> Result<FormatReport, ErrorKind> {
109 let mut timer = Timer::start();
110
111 let main_file = input.file_name();
112 let input_is_stdin = main_file == FileName::Stdin;
113
114 let psess = ParseSess::new(config)?;
115 if config.skip_children() && psess.ignore_file(&main_file) {
116 return Ok(FormatReport::new());
117 }
118
119 let mut report = FormatReport::new();
121 let directory_ownership = input.to_directory_ownership();
122
123 let krate = match Parser::parse_crate(input, &psess) {
124 Ok(krate) => krate,
125 Err(e) => {
127 let forbid_verbose = input_is_stdin || e != ParserError::ParsePanicError;
128 should_emit_verbose(forbid_verbose, config, || {
129 eprintln!("The Rust parser panicked");
130 });
131 report.add_parsing_error();
132 return Ok(report);
133 }
134 };
135
136 let mut context = FormatContext::new(&krate, report, psess, config, handler);
137 let files = modules::ModResolver::new(
138 &context.psess,
139 directory_ownership.unwrap_or(DirectoryOwnership::UnownedViaBlock),
140 !input_is_stdin && !config.skip_children(),
141 )
142 .visit_crate(&krate)?
143 .into_iter()
144 .filter(|(path, module)| {
145 input_is_stdin
146 || !should_skip_module(config, &context, input_is_stdin, &main_file, path, module)
147 })
148 .collect::<Vec<_>>();
149
150 timer = timer.done_parsing();
151
152 context.psess.set_silent_emitter();
154
155 for (path, module) in files {
156 if input_is_stdin && contains_skip(module.attrs()) {
157 return echo_back_stdin(context.psess.snippet_provider(module.span).entire_snippet());
158 }
159 should_emit_verbose(input_is_stdin, config, || println!("Formatting {}", path));
160 context.format_file(path, &module, is_macro_def)?;
161 }
162 timer = timer.done_formatting();
163
164 should_emit_verbose(input_is_stdin, config, || {
165 println!(
166 "Spent {0:.3} secs in the parsing phase, and {1:.3} secs in the formatting phase",
167 timer.get_parse_time(),
168 timer.get_format_time(),
169 )
170 });
171
172 Ok(context.report)
173}
174
175struct FormatContext<'a, T: FormatHandler> {
177 krate: &'a ast::Crate,
178 report: FormatReport,
179 psess: ParseSess,
180 config: &'a Config,
181 handler: &'a mut T,
182}
183
184impl<'a, T: FormatHandler + 'a> FormatContext<'a, T> {
185 fn new(
186 krate: &'a ast::Crate,
187 report: FormatReport,
188 psess: ParseSess,
189 config: &'a Config,
190 handler: &'a mut T,
191 ) -> Self {
192 FormatContext {
193 krate,
194 report,
195 psess,
196 config,
197 handler,
198 }
199 }
200
201 fn ignore_file(&self, path: &FileName) -> bool {
202 self.psess.ignore_file(path)
203 }
204
205 fn format_file(
207 &mut self,
208 path: FileName,
209 module: &Module<'_>,
210 is_macro_def: bool,
211 ) -> Result<(), ErrorKind> {
212 let snippet_provider = self.psess.snippet_provider(module.span);
213 let mut visitor = FmtVisitor::from_psess(
214 &self.psess,
215 self.config,
216 &snippet_provider,
217 self.report.clone(),
218 );
219 visitor.skip_context.update_with_attrs(&self.krate.attrs);
220 visitor.is_macro_def = is_macro_def;
221 visitor.last_pos = snippet_provider.start_pos();
222 visitor.skip_empty_lines(snippet_provider.end_pos());
223 visitor.format_separate_mod(module, snippet_provider.end_pos());
224
225 debug_assert_eq!(
226 visitor.line_number,
227 count_newlines(&visitor.buffer),
228 "failed in format_file visitor.buffer:\n {:?}",
229 &visitor.buffer
230 );
231
232 let num_newlines = count_newlines(&visitor.buffer);
235 if self
236 .config
237 .file_lines()
238 .contains_line(&path, num_newlines + 1)
239 {
240 source_file::append_newline(&mut visitor.buffer);
241 }
242
243 format_lines(
244 &mut visitor.buffer,
245 &path,
246 &visitor.skipped_range.borrow(),
247 self.config,
248 &self.report,
249 );
250
251 apply_newline_style(
252 self.config.newline_style(),
253 &mut visitor.buffer,
254 snippet_provider.entire_snippet(),
255 );
256
257 if visitor.macro_rewrite_failure {
258 self.report.add_macro_format_failure();
259 }
260 self.report
261 .add_non_formatted_ranges(visitor.skipped_range.borrow().clone());
262
263 self.handler.handle_formatted_file(
264 &self.psess,
265 path,
266 visitor.buffer.to_owned(),
267 &mut self.report,
268 )
269 }
270}
271
272trait FormatHandler {
274 fn handle_formatted_file(
275 &mut self,
276 psess: &ParseSess,
277 path: FileName,
278 result: String,
279 report: &mut FormatReport,
280 ) -> Result<(), ErrorKind>;
281}
282
283impl<'b, T: Write + 'b> FormatHandler for Session<'b, T> {
284 fn handle_formatted_file(
286 &mut self,
287 psess: &ParseSess,
288 path: FileName,
289 result: String,
290 report: &mut FormatReport,
291 ) -> Result<(), ErrorKind> {
292 if let Some(ref mut out) = self.out {
293 match source_file::write_file(
294 Some(psess),
295 &path,
296 &result,
297 out,
298 &mut *self.emitter,
299 self.config.newline_style(),
300 ) {
301 Ok(ref result) if result.has_diff => report.add_diff(),
302 Err(e) => {
303 let err_msg = format!("{path}: {e}");
305 return Err(io::Error::new(e.kind(), err_msg).into());
306 }
307 _ => {}
308 }
309 }
310
311 self.source_file.push((path, result));
312 Ok(())
313 }
314}
315
316pub(crate) struct FormattingError {
317 pub(crate) line: usize,
318 pub(crate) kind: ErrorKind,
319 is_comment: bool,
320 is_string: bool,
321 pub(crate) line_buffer: String,
322 pub(crate) highlight: Option<Range<usize>>,
324}
325
326impl FormattingError {
327 pub(crate) fn from_span(span: Span, psess: &ParseSess, kind: ErrorKind) -> FormattingError {
328 FormattingError {
329 line: psess.line_of_byte_pos(span.lo()),
330 is_comment: kind.is_comment(),
331 kind,
332 is_string: false,
333 line_buffer: psess.span_to_first_line_string(span),
334 highlight: None,
335 }
336 }
337
338 pub(crate) fn is_internal(&self) -> bool {
339 match self.kind {
340 ErrorKind::LineOverflow(..)
341 | ErrorKind::TrailingWhitespace
342 | ErrorKind::IoError(_)
343 | ErrorKind::ParseError
344 | ErrorKind::LostComment => true,
345 _ => false,
346 }
347 }
348
349 pub(crate) fn msg_suffix(&self) -> Option<&str> {
350 if self.is_comment || self.is_string {
351 Some(
352 "set `error_on_unformatted = false` to suppress \
353 the warning against comments or string literals",
354 )
355 } else {
356 None
357 }
358 }
359}
360
361pub(crate) type FormatErrorMap = HashMap<FileName, Vec<FormattingError>>;
362
363#[derive(Default, Debug, PartialEq)]
364pub(crate) struct ReportedErrors {
365 pub(crate) has_operational_errors: bool,
367
368 pub(crate) has_parsing_errors: bool,
370
371 pub(crate) has_formatting_errors: bool,
373
374 pub(crate) has_macro_format_failure: bool,
376
377 pub(crate) has_check_errors: bool,
379
380 pub(crate) has_diff: bool,
382
383 pub(crate) has_unformatted_code_errors: bool,
385}
386
387impl ReportedErrors {
388 pub(crate) fn add(&mut self, other: &ReportedErrors) {
390 self.has_operational_errors |= other.has_operational_errors;
391 self.has_parsing_errors |= other.has_parsing_errors;
392 self.has_formatting_errors |= other.has_formatting_errors;
393 self.has_macro_format_failure |= other.has_macro_format_failure;
394 self.has_check_errors |= other.has_check_errors;
395 self.has_diff |= other.has_diff;
396 self.has_unformatted_code_errors |= other.has_unformatted_code_errors;
397 }
398}
399
400#[derive(Clone, Copy, Debug)]
401enum Timer {
402 Disabled,
403 Initialized(Instant),
404 DoneParsing(Instant, Instant),
405 DoneFormatting(Instant, Instant, Instant),
406}
407
408impl Timer {
409 fn start() -> Timer {
410 if cfg!(target_arch = "wasm32") {
411 Timer::Disabled
412 } else {
413 Timer::Initialized(Instant::now())
414 }
415 }
416 fn done_parsing(self) -> Self {
417 match self {
418 Timer::Disabled => Timer::Disabled,
419 Timer::Initialized(init_time) => Timer::DoneParsing(init_time, Instant::now()),
420 _ => panic!("Timer can only transition to DoneParsing from Initialized state"),
421 }
422 }
423
424 fn done_formatting(self) -> Self {
425 match self {
426 Timer::Disabled => Timer::Disabled,
427 Timer::DoneParsing(init_time, parse_time) => {
428 Timer::DoneFormatting(init_time, parse_time, Instant::now())
429 }
430 _ => panic!("Timer can only transition to DoneFormatting from DoneParsing state"),
431 }
432 }
433
434 fn get_parse_time(&self) -> f32 {
436 match *self {
437 Timer::Disabled => panic!("this platform cannot time execution"),
438 Timer::DoneParsing(init, parse_time) | Timer::DoneFormatting(init, parse_time, _) => {
439 Self::duration_to_f32(parse_time.duration_since(init))
441 }
442 Timer::Initialized(..) => unreachable!(),
443 }
444 }
445
446 fn get_format_time(&self) -> f32 {
449 match *self {
450 Timer::Disabled => panic!("this platform cannot time execution"),
451 Timer::DoneFormatting(_init, parse_time, format_time) => {
452 Self::duration_to_f32(format_time.duration_since(parse_time))
453 }
454 Timer::DoneParsing(..) | Timer::Initialized(..) => unreachable!(),
455 }
456 }
457
458 fn duration_to_f32(d: Duration) -> f32 {
459 d.as_secs() as f32 + d.subsec_nanos() as f32 / 1_000_000_000f32
460 }
461}
462
463fn format_lines(
466 text: &mut String,
467 name: &FileName,
468 skipped_range: &[(usize, usize)],
469 config: &Config,
470 report: &FormatReport,
471) {
472 let mut formatter = FormatLines::new(name, skipped_range, config);
473 formatter.iterate(text);
474
475 if formatter.newline_count > 1 {
476 debug!("track truncate: {} {}", text.len(), formatter.newline_count);
477 let line = text.len() - formatter.newline_count + 1;
478 text.truncate(line);
479 }
480
481 report.append(name.clone(), formatter.errors);
482}
483
484struct FormatLines<'a> {
485 name: &'a FileName,
486 skipped_range: &'a [(usize, usize)],
487 whitespace_start: Option<usize>,
488 overflow_start: Option<usize>,
489 line_len: usize,
490 cur_line: usize,
491 newline_count: usize,
492 errors: Vec<FormattingError>,
493 line_buffer: String,
494 current_line_contains_string_literal: bool,
495 format_line: bool,
496 config: &'a Config,
497}
498
499impl<'a> FormatLines<'a> {
500 fn new(
501 name: &'a FileName,
502 skipped_range: &'a [(usize, usize)],
503 config: &'a Config,
504 ) -> FormatLines<'a> {
505 FormatLines {
506 name,
507 skipped_range,
508 whitespace_start: None,
509 overflow_start: None,
510 line_len: 0,
511 cur_line: 1,
512 newline_count: 0,
513 errors: vec![],
514 line_buffer: String::with_capacity(config.max_width() * 2),
515 current_line_contains_string_literal: false,
516 format_line: config.file_lines().contains_line(name, 1),
517 config,
518 }
519 }
520
521 fn iterate(&mut self, text: &mut String) {
523 for (kind, c) in CharClasses::new(text.chars()) {
524 if c == '\r' {
525 continue;
526 }
527
528 if c == '\n' {
529 self.new_line(kind);
530 } else {
531 self.char(c, kind);
532 }
533 }
534 }
535
536 fn new_line(&mut self, kind: FullCodeCharKind) {
537 if self.format_line {
538 if let Some(whitespace_start) = self.whitespace_start {
540 if self.should_report_error(kind, &ErrorKind::TrailingWhitespace)
541 && !self.is_skipped_line()
542 {
543 self.push_err(
544 ErrorKind::TrailingWhitespace,
545 kind.is_comment(),
546 kind.is_string(),
547 self.line_buffer.trim_end().len()..self.line_buffer.len(),
548 );
549 }
550 self.line_len = whitespace_start;
551 }
552
553 let error_kind = ErrorKind::LineOverflow(self.line_len, self.config.max_width());
555 if self.line_len > self.config.max_width()
556 && !self.is_skipped_line()
557 && self.should_report_error(kind, &error_kind)
558 {
559 let is_string = self.current_line_contains_string_literal;
560 let overflow_start = self
561 .overflow_start
562 .expect("overflow_start is set whenever the line exceeds max_width");
563 let highlight = overflow_start..self.line_buffer.trim_end().len();
564 self.push_err(error_kind, kind.is_comment(), is_string, highlight);
565 }
566 }
567
568 self.line_len = 0;
569 self.cur_line += 1;
570 self.format_line = self
571 .config
572 .file_lines()
573 .contains_line(self.name, self.cur_line);
574 self.newline_count += 1;
575 self.whitespace_start = None;
576 self.overflow_start = None;
577 self.line_buffer.clear();
578 self.current_line_contains_string_literal = false;
579 }
580
581 fn char(&mut self, c: char, kind: FullCodeCharKind) {
582 self.newline_count = 0;
583 if !c.is_whitespace() {
584 self.whitespace_start = None;
585 } else if self.whitespace_start.is_none() {
586 self.whitespace_start = Some(self.line_len);
587 }
588 self.line_len += if c == '\t' {
589 self.config.tab_spaces()
590 } else {
591 1
592 };
593 if self.line_len > self.config.max_width() && self.overflow_start.is_none() {
594 self.overflow_start = Some(self.line_buffer.len());
595 }
596 self.line_buffer.push(c);
597 if kind.is_string() {
598 self.current_line_contains_string_literal = true;
599 }
600 }
601
602 fn push_err(
603 &mut self,
604 kind: ErrorKind,
605 is_comment: bool,
606 is_string: bool,
607 highlight: Range<usize>,
608 ) {
609 self.errors.push(FormattingError {
610 line: self.cur_line,
611 kind,
612 is_comment,
613 is_string,
614 line_buffer: self.line_buffer.clone(),
615 highlight: Some(highlight),
616 });
617 }
618
619 fn should_report_error(&self, char_kind: FullCodeCharKind, error_kind: &ErrorKind) -> bool {
620 let allow_error_report = if char_kind.is_comment()
621 || self.current_line_contains_string_literal
622 || error_kind.is_comment()
623 {
624 self.config.error_on_unformatted()
625 } else {
626 true
627 };
628
629 match error_kind {
630 ErrorKind::LineOverflow(..) => {
631 self.config.error_on_line_overflow() && allow_error_report
632 }
633 ErrorKind::TrailingWhitespace | ErrorKind::LostComment => allow_error_report,
634 _ => true,
635 }
636 }
637
638 fn is_skipped_line(&self) -> bool {
640 self.skipped_range
641 .iter()
642 .any(|&(lo, hi)| lo <= self.cur_line && self.cur_line <= hi)
643 }
644}
645
646fn should_emit_verbose<F>(forbid_verbose_output: bool, config: &Config, f: F)
647where
648 F: Fn(),
649{
650 if config.verbose() == Verbosity::Verbose && !forbid_verbose_output {
651 f();
652 }
653}