rustfmt_nightly/
lib.rs

1#![feature(rustc_private)]
2#![deny(rust_2018_idioms)]
3#![warn(unreachable_pub)]
4#![recursion_limit = "256"]
5#![allow(clippy::match_like_matches_macro)]
6#![allow(unreachable_pub)]
7
8// N.B. these crates are loaded from the sysroot, so they need extern crate.
9extern crate rustc_ast;
10extern crate rustc_ast_pretty;
11extern crate rustc_data_structures;
12extern crate rustc_errors;
13extern crate rustc_expand;
14extern crate rustc_parse;
15extern crate rustc_session;
16extern crate rustc_span;
17extern crate thin_vec;
18
19// Necessary to pull in object code as the rest of the rustc crates are shipped only as rmeta
20// files.
21#[allow(unused_extern_crates)]
22extern crate rustc_driver;
23
24use std::cell::RefCell;
25use std::cmp::min;
26use std::collections::HashMap;
27use std::fmt;
28use std::io::{self, Write};
29use std::mem;
30use std::panic;
31use std::path::PathBuf;
32use std::rc::Rc;
33
34use rustc_ast::ast;
35use rustc_span::symbol;
36use thiserror::Error;
37
38use crate::comment::LineClasses;
39use crate::emitter::Emitter;
40use crate::formatting::{FormatErrorMap, FormattingError, ReportedErrors, SourceFile};
41use crate::modules::ModuleResolutionError;
42use crate::parse::parser::DirectoryOwnership;
43use crate::shape::Indent;
44use crate::utils::indent_next_line;
45
46pub use crate::config::{
47    CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName, NewlineStyle, Range,
48    StyleEdition, Verbosity, Version, load_config,
49};
50
51pub use crate::format_report_formatter::{FormatReportFormatter, FormatReportFormatterBuilder};
52
53pub use crate::rustfmt_diff::{ModifiedChunk, ModifiedLines};
54
55#[macro_use]
56mod utils;
57
58macro_rules! static_regex {
59    ($re:literal) => {{
60        static RE: ::std::sync::OnceLock<::regex::Regex> = ::std::sync::OnceLock::new();
61        RE.get_or_init(|| ::regex::Regex::new($re).unwrap())
62    }};
63}
64
65mod attr;
66mod chains;
67mod closures;
68mod comment;
69pub(crate) mod config;
70mod coverage;
71mod emitter;
72mod expr;
73mod format_report_formatter;
74pub(crate) mod formatting;
75mod ignore_path;
76mod imports;
77mod items;
78mod lists;
79mod macros;
80mod matches;
81mod missed_spans;
82pub(crate) mod modules;
83mod overflow;
84mod pairs;
85mod parse;
86mod patterns;
87mod release_channel;
88mod reorder;
89mod rewrite;
90pub(crate) mod rustfmt_diff;
91mod shape;
92mod skip;
93mod sort;
94pub(crate) mod source_file;
95pub(crate) mod source_map;
96mod spanned;
97mod stmt;
98mod string;
99#[cfg(test)]
100mod test;
101mod types;
102mod vertical;
103pub(crate) mod visitor;
104
105/// The various errors that can occur during formatting. Note that not all of
106/// these can currently be propagated to clients.
107#[derive(Error, Debug)]
108pub enum ErrorKind {
109    /// Line has exceeded character limit (found, maximum).
110    #[error(
111        "line formatted, but exceeded maximum width \
112         (maximum: {1} (see `max_width` option), found: {0})"
113    )]
114    LineOverflow(usize, usize),
115    /// Line ends in whitespace.
116    #[error("left behind trailing whitespace")]
117    TrailingWhitespace,
118    /// Used deprecated skip attribute.
119    #[error("`rustfmt_skip` is deprecated; use `rustfmt::skip`")]
120    DeprecatedAttr,
121    /// Used a rustfmt:: attribute other than skip or skip::macros.
122    #[error("invalid attribute")]
123    BadAttr,
124    /// An io error during reading or writing.
125    #[error("io error: {0}")]
126    IoError(io::Error),
127    /// Error during module resolution.
128    #[error("{0}")]
129    ModuleResolutionError(#[from] ModuleResolutionError),
130    /// Parse error occurred when parsing the input.
131    #[error("parse error")]
132    ParseError,
133    /// The user mandated a version and the current version of Rustfmt does not
134    /// satisfy that requirement.
135    #[error("version mismatch")]
136    VersionMismatch,
137    /// If we had formatted the given node, then we would have lost a comment.
138    #[error("not formatted because a comment would be lost")]
139    LostComment,
140    /// Invalid glob pattern in `ignore` configuration option.
141    #[error("Invalid glob pattern found in ignore list: {0}")]
142    InvalidGlobPattern(ignore::Error),
143}
144
145impl ErrorKind {
146    fn is_comment(&self) -> bool {
147        matches!(self, ErrorKind::LostComment)
148    }
149}
150
151impl From<io::Error> for ErrorKind {
152    fn from(e: io::Error) -> ErrorKind {
153        ErrorKind::IoError(e)
154    }
155}
156
157/// Result of formatting a snippet of code along with ranges of lines that didn't get formatted,
158/// i.e., that got returned as they were originally.
159#[derive(Debug)]
160struct FormattedSnippet {
161    snippet: String,
162    non_formatted_ranges: Vec<(usize, usize)>,
163}
164
165impl FormattedSnippet {
166    /// In case the snippet needed to be wrapped in a function, this shifts down the ranges of
167    /// non-formatted code.
168    fn unwrap_code_block(&mut self) {
169        self.non_formatted_ranges
170            .iter_mut()
171            .for_each(|(low, high)| {
172                *low -= 1;
173                *high -= 1;
174            });
175    }
176
177    /// Returns `true` if the line n did not get formatted.
178    fn is_line_non_formatted(&self, n: usize) -> bool {
179        self.non_formatted_ranges
180            .iter()
181            .any(|(low, high)| *low <= n && n <= *high)
182    }
183}
184
185/// Reports on any issues that occurred during a run of Rustfmt.
186///
187/// Can be reported to the user using the `Display` impl on [`FormatReportFormatter`].
188#[derive(Clone)]
189pub struct FormatReport {
190    // Maps stringified file paths to their associated formatting errors.
191    internal: Rc<RefCell<(FormatErrorMap, ReportedErrors)>>,
192    non_formatted_ranges: Vec<(usize, usize)>,
193}
194
195impl FormatReport {
196    fn new() -> FormatReport {
197        FormatReport {
198            internal: Rc::new(RefCell::new((HashMap::new(), ReportedErrors::default()))),
199            non_formatted_ranges: Vec::new(),
200        }
201    }
202
203    fn add_non_formatted_ranges(&mut self, mut ranges: Vec<(usize, usize)>) {
204        self.non_formatted_ranges.append(&mut ranges);
205    }
206
207    fn append(&self, f: FileName, mut v: Vec<FormattingError>) {
208        self.track_errors(&v);
209        self.internal
210            .borrow_mut()
211            .0
212            .entry(f)
213            .and_modify(|fe| fe.append(&mut v))
214            .or_insert(v);
215    }
216
217    fn track_errors(&self, new_errors: &[FormattingError]) {
218        let errs = &mut self.internal.borrow_mut().1;
219        if !new_errors.is_empty() {
220            errs.has_formatting_errors = true;
221        }
222        if errs.has_operational_errors && errs.has_check_errors && errs.has_unformatted_code_errors
223        {
224            return;
225        }
226        for err in new_errors {
227            match err.kind {
228                ErrorKind::LineOverflow(..) => {
229                    errs.has_operational_errors = true;
230                }
231                ErrorKind::TrailingWhitespace => {
232                    errs.has_operational_errors = true;
233                    errs.has_unformatted_code_errors = true;
234                }
235                ErrorKind::LostComment => {
236                    errs.has_unformatted_code_errors = true;
237                }
238                ErrorKind::DeprecatedAttr | ErrorKind::BadAttr | ErrorKind::VersionMismatch => {
239                    errs.has_check_errors = true;
240                }
241                _ => {}
242            }
243        }
244    }
245
246    fn add_diff(&mut self) {
247        self.internal.borrow_mut().1.has_diff = true;
248    }
249
250    fn add_macro_format_failure(&mut self) {
251        self.internal.borrow_mut().1.has_macro_format_failure = true;
252    }
253
254    fn add_parsing_error(&mut self) {
255        self.internal.borrow_mut().1.has_parsing_errors = true;
256    }
257
258    fn warning_count(&self) -> usize {
259        self.internal
260            .borrow()
261            .0
262            .values()
263            .map(|errors| errors.len())
264            .sum()
265    }
266
267    /// Whether any warnings or errors are present in the report.
268    pub fn has_warnings(&self) -> bool {
269        self.internal.borrow().1.has_formatting_errors
270    }
271
272    /// Print the report to a terminal using colours and potentially other
273    /// fancy output.
274    #[deprecated(note = "Use FormatReportFormatter with colors enabled instead")]
275    pub fn fancy_print(
276        &self,
277        mut t: Box<dyn term::Terminal<Output = io::Stderr>>,
278    ) -> Result<(), term::Error> {
279        writeln!(
280            t,
281            "{}",
282            FormatReportFormatterBuilder::new(self)
283                .enable_colors(true)
284                .build()
285        )?;
286        Ok(())
287    }
288}
289
290/// Deprecated - Use FormatReportFormatter instead
291// https://github.com/rust-lang/rust/issues/78625
292// https://github.com/rust-lang/rust/issues/39935
293impl fmt::Display for FormatReport {
294    // Prints all the formatting errors.
295    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
296        write!(fmt, "{}", FormatReportFormatterBuilder::new(self).build())?;
297        Ok(())
298    }
299}
300
301/// Format the given snippet. The snippet is expected to be *complete* code.
302/// When we cannot parse the given snippet, this function returns `None`.
303fn format_snippet(snippet: &str, config: &Config, is_macro_def: bool) -> Option<FormattedSnippet> {
304    let mut config = config.clone();
305    panic::catch_unwind(|| {
306        let mut out: Vec<u8> = Vec::with_capacity(snippet.len() * 2);
307        config.set().emit_mode(config::EmitMode::Stdout);
308        config.set().verbose(Verbosity::Quiet);
309        config.set().show_parse_errors(false);
310        if is_macro_def {
311            config.set().error_on_unformatted(true);
312        }
313
314        let (formatting_error, result) = {
315            let input = Input::Text(snippet.into());
316            let mut session = Session::new(config, Some(&mut out));
317            let result = session.format_input_inner(input, is_macro_def);
318            (
319                session.errors.has_macro_format_failure
320                    || session.out.as_ref().unwrap().is_empty() && !snippet.is_empty()
321                    || result.is_err()
322                    || (is_macro_def && session.has_unformatted_code_errors()),
323                result,
324            )
325        };
326        if formatting_error {
327            None
328        } else {
329            String::from_utf8(out).ok().map(|snippet| FormattedSnippet {
330                snippet,
331                non_formatted_ranges: result.unwrap().non_formatted_ranges,
332            })
333        }
334    })
335    // Discard panics encountered while formatting the snippet
336    // The ? operator is needed to remove the extra Option
337    .ok()?
338}
339
340/// Format the given code block. Mainly targeted for code block in comment.
341/// The code block may be incomplete (i.e., parser may be unable to parse it).
342/// To avoid panic in parser, we wrap the code block with a dummy function.
343/// The returned code block does **not** end with newline.
344fn format_code_block(
345    code_snippet: &str,
346    config: &Config,
347    is_macro_def: bool,
348) -> Option<FormattedSnippet> {
349    const FN_MAIN_PREFIX: &str = "fn main() {\n";
350
351    fn enclose_in_main_block(s: &str, config: &Config) -> String {
352        let indent = Indent::from_width(config, config.tab_spaces());
353        let mut result = String::with_capacity(s.len() * 2);
354        result.push_str(FN_MAIN_PREFIX);
355        let mut need_indent = true;
356        for (kind, line) in LineClasses::new(s) {
357            if need_indent {
358                result.push_str(&indent.to_string(config));
359            }
360            result.push_str(&line);
361            result.push('\n');
362            need_indent = indent_next_line(kind, &line, config);
363        }
364        result.push('}');
365        result
366    }
367
368    // Wrap the given code block with `fn main()` if it does not have one.
369    let snippet = enclose_in_main_block(code_snippet, config);
370    let mut result = String::with_capacity(snippet.len());
371    let mut is_first = true;
372
373    // While formatting the code, ignore the config's newline style setting and always use "\n"
374    // instead of "\r\n" for the newline characters. This is ok because the output here is
375    // not directly outputted by rustfmt command, but used by the comment formatter's input.
376    // We have output-file-wide "\n" ==> "\r\n" conversion process after here if it's necessary.
377    let mut config_with_unix_newline = config.clone();
378    config_with_unix_newline
379        .set()
380        .newline_style(NewlineStyle::Unix);
381    let mut formatted = format_snippet(&snippet, &config_with_unix_newline, is_macro_def)?;
382    // Remove wrapping main block
383    formatted.unwrap_code_block();
384
385    // Trim "fn main() {" on the first line and "}" on the last line,
386    // then unindent the whole code block.
387    let block_len = formatted
388        .snippet
389        .rfind('}')
390        .unwrap_or_else(|| formatted.snippet.len());
391
392    // It's possible that `block_len < FN_MAIN_PREFIX.len()`. This can happen if the code block was
393    // formatted into the empty string, leading to the enclosing `fn main() {\n}` being formatted
394    // into `fn main() {}`. In this case no unindentation is done.
395    let block_start = min(FN_MAIN_PREFIX.len(), block_len);
396
397    let mut is_indented = true;
398    let indent_str = Indent::from_width(config, config.tab_spaces()).to_string(config);
399    for (kind, ref line) in LineClasses::new(&formatted.snippet[block_start..block_len]) {
400        if !is_first {
401            result.push('\n');
402        } else {
403            is_first = false;
404        }
405        let trimmed_line = if !is_indented {
406            line
407        } else if line.len() > config.max_width() {
408            // If there are lines that are larger than max width, we cannot tell
409            // whether we have succeeded but have some comments or strings that
410            // are too long, or we have failed to format code block. We will be
411            // conservative and just return `None` in this case.
412            return None;
413        } else if line.len() > indent_str.len() {
414            // Make sure that the line has leading whitespaces.
415            if line.starts_with(indent_str.as_ref()) {
416                let offset = if config.hard_tabs() {
417                    1
418                } else {
419                    config.tab_spaces()
420                };
421                &line[offset..]
422            } else {
423                line
424            }
425        } else {
426            line
427        };
428        result.push_str(trimmed_line);
429        is_indented = indent_next_line(kind, line, config);
430    }
431    Some(FormattedSnippet {
432        snippet: result,
433        non_formatted_ranges: formatted.non_formatted_ranges,
434    })
435}
436
437/// A session is a run of rustfmt across a single or multiple inputs.
438pub struct Session<'b, T: Write> {
439    pub config: Config,
440    pub out: Option<&'b mut T>,
441    pub(crate) errors: ReportedErrors,
442    source_file: SourceFile,
443    emitter: Box<dyn Emitter + 'b>,
444}
445
446impl<'b, T: Write + 'b> Session<'b, T> {
447    pub fn new(config: Config, mut out: Option<&'b mut T>) -> Session<'b, T> {
448        let emitter = create_emitter(&config);
449
450        if let Some(ref mut out) = out {
451            let _ = emitter.emit_header(out);
452        }
453
454        Session {
455            config,
456            out,
457            emitter,
458            errors: ReportedErrors::default(),
459            source_file: SourceFile::new(),
460        }
461    }
462
463    /// The main entry point for Rustfmt. Formats the given input according to the
464    /// given config. `out` is only necessary if required by the configuration.
465    pub fn format(&mut self, input: Input) -> Result<FormatReport, ErrorKind> {
466        self.format_input_inner(input, false)
467    }
468
469    pub fn override_config<F, U>(&mut self, mut config: Config, f: F) -> U
470    where
471        F: FnOnce(&mut Session<'b, T>) -> U,
472    {
473        mem::swap(&mut config, &mut self.config);
474        let result = f(self);
475        mem::swap(&mut config, &mut self.config);
476        result
477    }
478
479    pub fn add_operational_error(&mut self) {
480        self.errors.has_operational_errors = true;
481    }
482
483    pub fn has_operational_errors(&self) -> bool {
484        self.errors.has_operational_errors
485    }
486
487    pub fn has_parsing_errors(&self) -> bool {
488        self.errors.has_parsing_errors
489    }
490
491    pub fn has_formatting_errors(&self) -> bool {
492        self.errors.has_formatting_errors
493    }
494
495    pub fn has_check_errors(&self) -> bool {
496        self.errors.has_check_errors
497    }
498
499    pub fn has_diff(&self) -> bool {
500        self.errors.has_diff
501    }
502
503    pub fn has_unformatted_code_errors(&self) -> bool {
504        self.errors.has_unformatted_code_errors
505    }
506
507    pub fn has_no_errors(&self) -> bool {
508        !(self.has_operational_errors()
509            || self.has_parsing_errors()
510            || self.has_formatting_errors()
511            || self.has_check_errors()
512            || self.has_diff()
513            || self.has_unformatted_code_errors()
514            || self.errors.has_macro_format_failure)
515    }
516}
517
518pub(crate) fn create_emitter<'a>(config: &Config) -> Box<dyn Emitter + 'a> {
519    match config.emit_mode() {
520        EmitMode::Files if config.make_backup() => {
521            Box::new(emitter::FilesWithBackupEmitter::default())
522        }
523        EmitMode::Files => Box::new(emitter::FilesEmitter::new(
524            config.print_misformatted_file_names(),
525        )),
526        EmitMode::Stdout | EmitMode::Coverage => {
527            Box::new(emitter::StdoutEmitter::new(config.verbose()))
528        }
529        EmitMode::Json => Box::new(emitter::JsonEmitter::default()),
530        EmitMode::ModifiedLines => Box::new(emitter::ModifiedLinesEmitter::default()),
531        EmitMode::Checkstyle => Box::new(emitter::CheckstyleEmitter::default()),
532        EmitMode::Diff => Box::new(emitter::DiffEmitter::new(config.clone())),
533    }
534}
535
536impl<'b, T: Write + 'b> Drop for Session<'b, T> {
537    fn drop(&mut self) {
538        if let Some(ref mut out) = self.out {
539            let _ = self.emitter.emit_footer(out);
540        }
541    }
542}
543
544#[derive(Debug)]
545pub enum Input {
546    File(PathBuf),
547    Text(String),
548}
549
550impl Input {
551    fn file_name(&self) -> FileName {
552        match *self {
553            Input::File(ref file) => FileName::Real(file.clone()),
554            Input::Text(..) => FileName::Stdin,
555        }
556    }
557
558    fn to_directory_ownership(&self) -> Option<DirectoryOwnership> {
559        match self {
560            Input::File(ref file) => {
561                // If there exists a directory with the same name as an input,
562                // then the input should be parsed as a sub module.
563                let file_stem = file.file_stem()?;
564                if file.parent()?.to_path_buf().join(file_stem).is_dir() {
565                    Some(DirectoryOwnership::Owned {
566                        relative: file_stem.to_str().map(symbol::Ident::from_str),
567                    })
568                } else {
569                    None
570                }
571            }
572            _ => None,
573        }
574    }
575}
576
577#[cfg(test)]
578mod unit_tests {
579    use super::*;
580
581    #[test]
582    fn test_no_panic_on_format_snippet_and_format_code_block() {
583        // `format_snippet()` and `format_code_block()` should not panic
584        // even when we cannot parse the given snippet.
585        let snippet = "let";
586        assert!(format_snippet(snippet, &Config::default(), false).is_none());
587        assert!(format_code_block(snippet, &Config::default(), false).is_none());
588    }
589
590    fn test_format_inner<F>(formatter: F, input: &str, expected: &str) -> bool
591    where
592        F: Fn(&str, &Config, bool) -> Option<FormattedSnippet>,
593    {
594        let output = formatter(input, &Config::default(), false);
595        output.is_some() && output.unwrap().snippet == expected
596    }
597
598    #[test]
599    fn test_format_snippet() {
600        let snippet = "fn main() { println!(\"hello, world\"); }";
601        #[cfg(not(windows))]
602        let expected = "fn main() {\n    \
603                        println!(\"hello, world\");\n\
604                        }\n";
605        #[cfg(windows)]
606        let expected = "fn main() {\r\n    \
607                        println!(\"hello, world\");\r\n\
608                        }\r\n";
609        assert!(test_format_inner(format_snippet, snippet, expected));
610    }
611
612    #[test]
613    fn test_format_code_block_fail() {
614        #[rustfmt::skip]
615        let code_block = "this_line_is_100_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(x, y, z);";
616        assert!(format_code_block(code_block, &Config::default(), false).is_none());
617    }
618
619    #[test]
620    fn test_format_code_block() {
621        // simple code block
622        let code_block = "let x=3;";
623        let expected = "let x = 3;";
624        assert!(test_format_inner(format_code_block, code_block, expected));
625
626        // more complex code block, taken from chains.rs.
627        let code_block =
628"let (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) {
629(
630chain_indent(context, shape.add_offset(parent_rewrite.len())),
631context.config.indent_style() == IndentStyle::Visual || is_small_parent,
632)
633} else if is_block_expr(context, &parent, &parent_rewrite) {
634match context.config.indent_style() {
635// Try to put the first child on the same line with parent's last line
636IndentStyle::Block => (parent_shape.block_indent(context.config.tab_spaces()), true),
637// The parent is a block, so align the rest of the chain with the closing
638// brace.
639IndentStyle::Visual => (parent_shape, false),
640}
641} else {
642(
643chain_indent(context, shape.add_offset(parent_rewrite.len())),
644false,
645)
646};
647";
648        let expected =
649"let (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) {
650    (
651        chain_indent(context, shape.add_offset(parent_rewrite.len())),
652        context.config.indent_style() == IndentStyle::Visual || is_small_parent,
653    )
654} else if is_block_expr(context, &parent, &parent_rewrite) {
655    match context.config.indent_style() {
656        // Try to put the first child on the same line with parent's last line
657        IndentStyle::Block => (parent_shape.block_indent(context.config.tab_spaces()), true),
658        // The parent is a block, so align the rest of the chain with the closing
659        // brace.
660        IndentStyle::Visual => (parent_shape, false),
661    }
662} else {
663    (
664        chain_indent(context, shape.add_offset(parent_rewrite.len())),
665        false,
666    )
667};";
668        assert!(test_format_inner(format_code_block, code_block, expected));
669    }
670}