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