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