]> git.lizzy.rs Git - rust.git/blob - src/lib.rs
Remove BlockIndentStyle::Inherit
[rust.git] / src / lib.rs
1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 // TODO we're going to allocate a whole bunch of temp Strings, is it worth
12 // keeping some scratch mem for this and running our own StrPool?
13 // TODO for lint violations of names, emit a refactor script
14
15 #[macro_use]
16 extern crate log;
17
18 extern crate syntex_syntax as syntax;
19 extern crate syntex_errors as errors;
20 extern crate rustc_serialize;
21
22 extern crate strings;
23
24 extern crate unicode_segmentation;
25 extern crate regex;
26 extern crate diff;
27 extern crate term;
28 extern crate itertools;
29 extern crate multimap;
30
31 use errors::{Handler, DiagnosticBuilder};
32 use errors::emitter::{ColorConfig, EmitterWriter};
33 use syntax::ast;
34 use syntax::codemap::{mk_sp, CodeMap, Span};
35 use syntax::parse::{self, ParseSess};
36
37 use strings::string_buffer::StringBuffer;
38
39 use std::io::{self, stdout, Write};
40 use std::ops::{Add, Sub};
41 use std::path::{Path, PathBuf};
42 use std::rc::Rc;
43 use std::collections::HashMap;
44 use std::fmt;
45
46 use issues::{BadIssueSeeker, Issue};
47 use filemap::FileMap;
48 use visitor::FmtVisitor;
49 use config::Config;
50 use checkstyle::{output_header, output_footer};
51
52 pub use self::summary::Summary;
53
54 #[macro_use]
55 mod utils;
56 pub mod config;
57 pub mod codemap;
58 pub mod filemap;
59 pub mod file_lines;
60 pub mod visitor;
61 mod checkstyle;
62 mod items;
63 mod missed_spans;
64 mod lists;
65 mod types;
66 mod expr;
67 mod imports;
68 mod issues;
69 mod rewrite;
70 mod string;
71 mod comment;
72 pub mod modules;
73 pub mod rustfmt_diff;
74 mod chains;
75 mod macros;
76 mod patterns;
77 mod summary;
78
79 const MIN_STRING: usize = 10;
80 // When we get scoped annotations, we should have rustfmt::skip.
81 const SKIP_ANNOTATION: &'static str = "rustfmt_skip";
82
83 pub trait Spanned {
84     fn span(&self) -> Span;
85 }
86
87 impl Spanned for ast::Expr {
88     fn span(&self) -> Span {
89         self.span
90     }
91 }
92
93 impl Spanned for ast::Pat {
94     fn span(&self) -> Span {
95         self.span
96     }
97 }
98
99 impl Spanned for ast::Ty {
100     fn span(&self) -> Span {
101         self.span
102     }
103 }
104
105 impl Spanned for ast::Arg {
106     fn span(&self) -> Span {
107         if items::is_named_arg(self) {
108             mk_sp(self.pat.span.lo, self.ty.span.hi)
109         } else {
110             self.ty.span
111         }
112     }
113 }
114
115 #[derive(Copy, Clone, Debug)]
116 pub struct Indent {
117     // Width of the block indent, in characters. Must be a multiple of
118     // Config::tab_spaces.
119     pub block_indent: usize,
120     // Alignment in characters.
121     pub alignment: usize,
122 }
123
124 impl Indent {
125     pub fn new(block_indent: usize, alignment: usize) -> Indent {
126         Indent {
127             block_indent: block_indent,
128             alignment: alignment,
129         }
130     }
131
132     pub fn empty() -> Indent {
133         Indent::new(0, 0)
134     }
135
136     pub fn block_only(&self) -> Indent {
137         Indent {
138             block_indent: self.block_indent,
139             alignment: 0,
140         }
141     }
142
143     pub fn block_indent(mut self, config: &Config) -> Indent {
144         self.block_indent += config.tab_spaces;
145         self
146     }
147
148     pub fn block_unindent(mut self, config: &Config) -> Indent {
149         self.block_indent -= config.tab_spaces;
150         self
151     }
152
153     pub fn width(&self) -> usize {
154         self.block_indent + self.alignment
155     }
156
157     pub fn to_string(&self, config: &Config) -> String {
158         let (num_tabs, num_spaces) = if config.hard_tabs {
159             (self.block_indent / config.tab_spaces, self.alignment)
160         } else {
161             (0, self.block_indent + self.alignment)
162         };
163         let num_chars = num_tabs + num_spaces;
164         let mut indent = String::with_capacity(num_chars);
165         for _ in 0..num_tabs {
166             indent.push('\t')
167         }
168         for _ in 0..num_spaces {
169             indent.push(' ')
170         }
171         indent
172     }
173 }
174
175 impl Add for Indent {
176     type Output = Indent;
177
178     fn add(self, rhs: Indent) -> Indent {
179         Indent {
180             block_indent: self.block_indent + rhs.block_indent,
181             alignment: self.alignment + rhs.alignment,
182         }
183     }
184 }
185
186 impl Sub for Indent {
187     type Output = Indent;
188
189     fn sub(self, rhs: Indent) -> Indent {
190         Indent::new(self.block_indent - rhs.block_indent,
191                     self.alignment - rhs.alignment)
192     }
193 }
194
195 impl Add<usize> for Indent {
196     type Output = Indent;
197
198     fn add(self, rhs: usize) -> Indent {
199         Indent::new(self.block_indent, self.alignment + rhs)
200     }
201 }
202
203 impl Sub<usize> for Indent {
204     type Output = Indent;
205
206     fn sub(self, rhs: usize) -> Indent {
207         Indent::new(self.block_indent, self.alignment - rhs)
208     }
209 }
210
211 #[derive(Copy, Clone, Debug)]
212 pub struct Shape {
213     pub width: usize,
214     // The current indentation of code.
215     pub indent: Indent,
216     // Indentation + any already emitted text on the first line of the current
217     // statement.
218     pub offset: usize,
219 }
220
221 impl Shape {
222     pub fn indented(indent: Indent, config: &Config) -> Shape {
223         Shape {
224             width: config.max_width,
225             indent: indent,
226             offset: indent.width(),
227         }
228     }
229
230     /// `indent` is the indentation of the first line. The next lines
231     /// should begin with at least `indent` spaces (except backwards
232     /// indentation). The first line should not begin with indentation.
233     /// `width` is the maximum number of characters on the last line
234     /// (excluding `indent`). The width of other lines is not limited by
235     /// `width`.
236     /// Note that in reality, we sometimes use width for lines other than the
237     /// last (i.e., we are conservative).
238     // .......*-------*
239     //        |       |
240     //        |     *-*
241     //        *-----|
242     // |<------------>|  max width
243     // |<---->|          indent
244     //        |<--->|    width
245     pub fn legacy(width: usize, indent: Indent) -> Shape {
246         Shape {
247             width: width,
248             indent: indent,
249             offset: indent.alignment,
250         }
251     }
252
253     pub fn offset(width: usize, indent: Indent, offset: usize) -> Shape {
254         Shape {
255             width: width,
256             indent: indent,
257             offset: offset,
258         }
259     }
260
261     pub fn visual_indent(&self, extra_width: usize) -> Shape {
262         let alignment = self.offset + extra_width;
263         Shape {
264             width: self.width,
265             indent: Indent {
266                 block_indent: self.indent.block_indent,
267                 alignment: alignment,
268             },
269             offset: alignment,
270         }
271     }
272
273     pub fn block_indent(&self, extra_width: usize) -> Shape {
274         if self.indent.alignment == 0 {
275             Shape {
276                 width: self.width,
277                 indent: Indent {
278                     block_indent: self.indent.block_indent + extra_width,
279                     alignment: 0,
280                 },
281                 offset: 0,
282             }
283         } else {
284             Shape {
285                 width: self.width,
286                 indent: Indent {
287                     block_indent: self.indent.block_indent,
288                     alignment: self.indent.alignment + extra_width,
289                 },
290                 offset: self.indent.alignment + extra_width,
291             }
292         }
293     }
294
295     pub fn add_offset(&self, extra_width: usize) -> Shape {
296         Shape {
297             width: self.width,
298             indent: Indent {
299                 block_indent: self.indent.block_indent,
300                 alignment: self.indent.alignment,
301             },
302             offset: self.offset + extra_width,
303         }
304     }
305
306     pub fn block(&self) -> Shape {
307         Shape {
308             width: self.width,
309             indent: Indent {
310                 block_indent: self.indent.block_indent,
311                 alignment: 0,
312             },
313             offset: self.offset,
314         }
315     }
316
317     pub fn sub_width(&self, width: usize) -> Option<Shape> {
318         Some(Shape {
319                  width: try_opt!(self.width.checked_sub(width)),
320                  indent: self.indent,
321                  offset: self.offset,
322              })
323     }
324
325     pub fn shrink_left(&self, width: usize) -> Option<Shape> {
326         Some(Shape {
327                  width: try_opt!(self.width.checked_sub(width)),
328                  indent: self.indent + width,
329                  offset: self.offset + width,
330              })
331     }
332
333     pub fn used_width(&self) -> usize {
334         self.indent.block_indent + self.offset
335     }
336 }
337
338 pub enum ErrorKind {
339     // Line has exceeded character limit (found, maximum)
340     LineOverflow(usize, usize),
341     // Line ends in whitespace
342     TrailingWhitespace,
343     // TO-DO or FIX-ME item without an issue number
344     BadIssue(Issue),
345 }
346
347 impl fmt::Display for ErrorKind {
348     fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
349         match *self {
350             ErrorKind::LineOverflow(found, maximum) => {
351                 write!(fmt,
352                        "line exceeded maximum length (maximum: {}, found: {})",
353                        maximum,
354                        found)
355             }
356             ErrorKind::TrailingWhitespace => write!(fmt, "left behind trailing whitespace"),
357             ErrorKind::BadIssue(issue) => write!(fmt, "found {}", issue),
358         }
359     }
360 }
361
362 // Formatting errors that are identified *after* rustfmt has run.
363 pub struct FormattingError {
364     line: u32,
365     kind: ErrorKind,
366 }
367
368 impl FormattingError {
369     fn msg_prefix(&self) -> &str {
370         match self.kind {
371             ErrorKind::LineOverflow(..) |
372             ErrorKind::TrailingWhitespace => "Rustfmt failed at",
373             ErrorKind::BadIssue(_) => "WARNING:",
374         }
375     }
376
377     fn msg_suffix(&self) -> &str {
378         match self.kind {
379             ErrorKind::LineOverflow(..) |
380             ErrorKind::TrailingWhitespace => "(sorry)",
381             ErrorKind::BadIssue(_) => "",
382         }
383     }
384 }
385
386 pub struct FormatReport {
387     // Maps stringified file paths to their associated formatting errors.
388     file_error_map: HashMap<String, Vec<FormattingError>>,
389 }
390
391 impl FormatReport {
392     fn new() -> FormatReport {
393         FormatReport { file_error_map: HashMap::new() }
394     }
395
396     pub fn warning_count(&self) -> usize {
397         self.file_error_map
398             .iter()
399             .map(|(_, errors)| errors.len())
400             .fold(0, |acc, x| acc + x)
401     }
402
403     pub fn has_warnings(&self) -> bool {
404         self.warning_count() > 0
405     }
406 }
407
408 impl fmt::Display for FormatReport {
409     // Prints all the formatting errors.
410     fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
411         for (file, errors) in &self.file_error_map {
412             for error in errors {
413                 try!(write!(fmt,
414                             "{} {}:{}: {} {}\n",
415                             error.msg_prefix(),
416                             file,
417                             error.line,
418                             error.kind,
419                             error.msg_suffix()));
420             }
421         }
422         Ok(())
423     }
424 }
425
426 // Formatting which depends on the AST.
427 fn format_ast<F>(krate: &ast::Crate,
428                  parse_session: &ParseSess,
429                  main_file: &Path,
430                  config: &Config,
431                  mut after_file: F)
432                  -> Result<(FileMap, bool), io::Error>
433     where F: FnMut(&str, &mut StringBuffer) -> Result<bool, io::Error>
434 {
435     let mut result = FileMap::new();
436     // diff mode: check if any files are differing
437     let mut has_diff = false;
438
439     // We always skip children for the "Plain" write mode, since there is
440     // nothing to distinguish the nested module contents.
441     let skip_children = config.skip_children || config.write_mode == config::WriteMode::Plain;
442     for (path, module) in modules::list_files(krate, parse_session.codemap()) {
443         if skip_children && path.as_path() != main_file {
444             continue;
445         }
446         let path = path.to_str().unwrap();
447         if config.verbose {
448             println!("Formatting {}", path);
449         }
450         let mut visitor = FmtVisitor::from_codemap(parse_session, config);
451         visitor.format_separate_mod(module);
452
453         has_diff |= try!(after_file(path, &mut visitor.buffer));
454
455         result.push((path.to_owned(), visitor.buffer));
456     }
457
458     Ok((result, has_diff))
459 }
460
461 // Formatting done on a char by char or line by line basis.
462 // FIXME(#209) warn on bad license
463 // FIXME(#20) other stuff for parity with make tidy
464 fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &mut FormatReport) {
465     // Iterate over the chars in the file map.
466     let mut trims = vec![];
467     let mut last_wspace: Option<usize> = None;
468     let mut line_len = 0;
469     let mut cur_line = 1;
470     let mut newline_count = 0;
471     let mut errors = vec![];
472     let mut issue_seeker = BadIssueSeeker::new(config.report_todo, config.report_fixme);
473
474     for (c, b) in text.chars() {
475         if c == '\r' {
476             line_len += c.len_utf8();
477             continue;
478         }
479
480         // Add warnings for bad todos/ fixmes
481         if let Some(issue) = issue_seeker.inspect(c) {
482             errors.push(FormattingError {
483                             line: cur_line,
484                             kind: ErrorKind::BadIssue(issue),
485                         });
486         }
487
488         if c == '\n' {
489             // Check for (and record) trailing whitespace.
490             if let Some(lw) = last_wspace {
491                 trims.push((cur_line, lw, b));
492                 line_len -= b - lw;
493             }
494             // Check for any line width errors we couldn't correct.
495             if config.error_on_line_overflow && line_len > config.max_width {
496                 errors.push(FormattingError {
497                                 line: cur_line,
498                                 kind: ErrorKind::LineOverflow(line_len, config.max_width),
499                             });
500             }
501             line_len = 0;
502             cur_line += 1;
503             newline_count += 1;
504             last_wspace = None;
505         } else {
506             newline_count = 0;
507             line_len += c.len_utf8();
508             if c.is_whitespace() {
509                 if last_wspace.is_none() {
510                     last_wspace = Some(b);
511                 }
512             } else {
513                 last_wspace = None;
514             }
515         }
516     }
517
518     if newline_count > 1 {
519         debug!("track truncate: {} {}", text.len, newline_count);
520         let line = text.len - newline_count + 1;
521         text.truncate(line);
522     }
523
524     for &(l, _, _) in &trims {
525         errors.push(FormattingError {
526                         line: l,
527                         kind: ErrorKind::TrailingWhitespace,
528                     });
529     }
530
531     report.file_error_map.insert(name.to_owned(), errors);
532 }
533
534 fn parse_input(input: Input,
535                parse_session: &ParseSess)
536                -> Result<ast::Crate, Option<DiagnosticBuilder>> {
537     let result = match input {
538         Input::File(file) => {
539             let mut parser = parse::new_parser_from_file(parse_session, &file);
540             parser.cfg_mods = false;
541             parser.parse_crate_mod()
542         }
543         Input::Text(text) => {
544             let mut parser =
545                 parse::new_parser_from_source_str(parse_session, "stdin".to_owned(), text);
546             parser.cfg_mods = false;
547             parser.parse_crate_mod()
548         }
549     };
550
551     match result {
552         Ok(c) => {
553             if parse_session.span_diagnostic.has_errors() {
554                 // Bail out if the parser recovered from an error.
555                 Err(None)
556             } else {
557                 Ok(c)
558             }
559         }
560         Err(e) => Err(Some(e)),
561     }
562 }
563
564 pub fn format_input<T: Write>(input: Input,
565                               config: &Config,
566                               mut out: Option<&mut T>)
567                               -> Result<(Summary, FileMap, FormatReport), (io::Error, Summary)> {
568     let mut summary = Summary::new();
569     if config.disable_all_formatting {
570         return Ok((summary, FileMap::new(), FormatReport::new()));
571     }
572     let codemap = Rc::new(CodeMap::new());
573
574     let tty_handler =
575         Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(codemap.clone()));
576     let mut parse_session = ParseSess::with_span_handler(tty_handler, codemap.clone());
577
578     let main_file = match input {
579         Input::File(ref file) => file.clone(),
580         Input::Text(..) => PathBuf::from("stdin"),
581     };
582
583     let krate = match parse_input(input, &parse_session) {
584         Ok(krate) => krate,
585         Err(diagnostic) => {
586             if let Some(mut diagnostic) = diagnostic {
587                 diagnostic.emit();
588             }
589             summary.add_parsing_error();
590             return Ok((summary, FileMap::new(), FormatReport::new()));
591         }
592     };
593
594     if parse_session.span_diagnostic.has_errors() {
595         summary.add_parsing_error();
596     }
597
598     // Suppress error output after parsing.
599     let silent_emitter = Box::new(EmitterWriter::new(Box::new(Vec::new()), Some(codemap.clone())));
600     parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter);
601
602     let mut report = FormatReport::new();
603
604     match format_ast(&krate,
605                      &parse_session,
606                      &main_file,
607                      config,
608                      |file_name, file| {
609         // For some reason, the codemap does not include terminating
610         // newlines so we must add one on for each file. This is sad.
611         filemap::append_newline(file);
612
613         format_lines(file, file_name, config, &mut report);
614
615         if let Some(ref mut out) = out {
616             return filemap::write_file(file, file_name, out, config);
617         }
618         Ok(false)
619     }) {
620         Ok((file_map, has_diff)) => {
621             if report.has_warnings() {
622                 summary.add_formatting_error();
623             }
624
625             if has_diff {
626                 summary.add_diff();
627             }
628
629             Ok((summary, file_map, report))
630         }
631         Err(e) => Err((e, summary)),
632     }
633 }
634
635 #[derive(Debug)]
636 pub enum Input {
637     File(PathBuf),
638     Text(String),
639 }
640
641 pub fn run(input: Input, config: &Config) -> Summary {
642     let mut out = &mut stdout();
643     output_header(out, config.write_mode).ok();
644     match format_input(input, config, Some(out)) {
645         Ok((summary, _, report)) => {
646             output_footer(out, config.write_mode).ok();
647
648             if report.has_warnings() {
649                 msg!("{}", report);
650             }
651
652             summary
653         }
654         Err((msg, mut summary)) => {
655             msg!("Error writing files: {}", msg);
656             summary.add_operational_error();
657             summary
658         }
659     }
660 }
661
662 #[cfg(test)]
663 mod test {
664     use super::*;
665
666     #[test]
667     fn indent_add_sub() {
668         let indent = Indent::new(4, 8) + Indent::new(8, 12);
669         assert_eq!(12, indent.block_indent);
670         assert_eq!(20, indent.alignment);
671
672         let indent = indent - Indent::new(4, 4);
673         assert_eq!(8, indent.block_indent);
674         assert_eq!(16, indent.alignment);
675     }
676
677     #[test]
678     fn indent_add_sub_alignment() {
679         let indent = Indent::new(4, 8) + 4;
680         assert_eq!(4, indent.block_indent);
681         assert_eq!(12, indent.alignment);
682
683         let indent = indent - 4;
684         assert_eq!(4, indent.block_indent);
685         assert_eq!(8, indent.alignment);
686     }
687
688     #[test]
689     fn indent_to_string_spaces() {
690         let config = Config::default();
691         let indent = Indent::new(4, 8);
692
693         // 12 spaces
694         assert_eq!("            ", indent.to_string(&config));
695     }
696
697     #[test]
698     fn indent_to_string_hard_tabs() {
699         let mut config = Config::default();
700         config.hard_tabs = true;
701         let indent = Indent::new(8, 4);
702
703         // 2 tabs + 4 spaces
704         assert_eq!("\t\t    ", indent.to_string(&config));
705     }
706
707     #[test]
708     fn shape_visual_indent() {
709         let config = Config::default();
710         let indent = Indent::new(4, 8);
711         let shape = Shape::indented(indent, &config);
712         let shape = shape.visual_indent(20);
713
714         assert_eq!(config.max_width, shape.width);
715         assert_eq!(4, shape.indent.block_indent);
716         assert_eq!(32, shape.indent.alignment);
717         assert_eq!(32, shape.offset);
718     }
719
720     #[test]
721     fn shape_block_indent_without_alignment() {
722         let config = Config::default();
723         let indent = Indent::new(4, 0);
724         let shape = Shape::indented(indent, &config);
725         let shape = shape.block_indent(20);
726
727         assert_eq!(config.max_width, shape.width);
728         assert_eq!(24, shape.indent.block_indent);
729         assert_eq!(0, shape.indent.alignment);
730         assert_eq!(0, shape.offset);
731     }
732
733     #[test]
734     fn shape_block_indent_with_alignment() {
735         let config = Config::default();
736         let indent = Indent::new(4, 8);
737         let shape = Shape::indented(indent, &config);
738         let shape = shape.block_indent(20);
739
740         assert_eq!(config.max_width, shape.width);
741         assert_eq!(4, shape.indent.block_indent);
742         assert_eq!(28, shape.indent.alignment);
743         assert_eq!(28, shape.offset);
744     }
745 }