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