Skip to main content

rustfmt_nightly/
formatting.rs

1// High level formatting functions.
2
3use 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
26// A map of the files of a crate, with their new content
27pub(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                // When the input is from stdin, echo back the input.
43                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
60/// Determine if a module should be skipped. True if the module should be skipped, false otherwise.
61fn 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    // FIXME(calebcartwright) - we need to determine how we'll handle the
82    // `format_generated_files` option with stdin based input.
83    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
102// Format an entire crate (or subset of the module tree).
103fn 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    // Parse the crate.
120    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        // Surface parse error via Session (errors are merged there from report)
126        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    // Suppress error output if we have to do any further parsing.
153    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
175// Used for formatting files.
176struct 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    // Formats a single file/module.
206    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        // For some reason, the source_map does not include terminating
233        // newlines so we must add one on for each file. This is sad.
234        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
272// Handle the results of formatting.
273trait 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    // Called for each formatted file.
285    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                    // Create a new error with path_str to help users see which files failed
304                    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    /// The byte range within `line_buffer` that the error should highlight
323    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    // Encountered e.g., an IO error.
366    pub(crate) has_operational_errors: bool,
367
368    // Failed to reformat code because of parsing errors.
369    pub(crate) has_parsing_errors: bool,
370
371    // Code is valid, but it is impossible to format it properly.
372    pub(crate) has_formatting_errors: bool,
373
374    // Code contains macro call that was unable to format.
375    pub(crate) has_macro_format_failure: bool,
376
377    // Failed an opt-in checking.
378    pub(crate) has_check_errors: bool,
379
380    /// Formatted code differs from existing code (--check only).
381    pub(crate) has_diff: bool,
382
383    /// Formatted code missed something, like lost comments or extra trailing space
384    pub(crate) has_unformatted_code_errors: bool,
385}
386
387impl ReportedErrors {
388    /// Combine two summaries together.
389    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    /// Returns the time it took to parse the source files in seconds.
435    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                // This should never underflow since `Instant::now()` guarantees monotonicity.
440                Self::duration_to_f32(parse_time.duration_since(init))
441            }
442            Timer::Initialized(..) => unreachable!(),
443        }
444    }
445
446    /// Returns the time it took to go from the parsed AST to the formatted output. Parsing time is
447    /// not included.
448    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
463// Formatting done on a char by char or line by line basis.
464// FIXME(#20): other stuff for parity with make tidy.
465fn 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    // Iterate over the chars in the file map.
522    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            // Check for (and record) trailing whitespace.
539            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            // Check for any line width errors we couldn't correct.
554            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    /// Returns `true` if the line with the given line number was skipped by `#[rustfmt::skip]`.
639    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}