]> git.lizzy.rs Git - rust.git/blob - src/lib.rs
3443db338cfd3d5f025865f10adf826a874751a4
[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 rustc_serialize;
20
21 extern crate strings;
22
23 extern crate unicode_segmentation;
24 extern crate regex;
25 extern crate diff;
26 extern crate term;
27
28 use syntax::ast;
29 use syntax::codemap::{mk_sp, CodeMap, Span};
30 use syntax::errors::Handler;
31 use syntax::errors::emitter::{ColorConfig, EmitterWriter};
32 use syntax::parse::{self, ParseSess};
33
34 use std::io::stdout;
35 use std::ops::{Add, Sub};
36 use std::path::Path;
37 use std::rc::Rc;
38 use std::collections::HashMap;
39 use std::fmt;
40
41 use issues::{BadIssueSeeker, Issue};
42 use filemap::FileMap;
43 use visitor::FmtVisitor;
44 use config::Config;
45
46 #[macro_use]
47 mod utils;
48 pub mod config;
49 pub mod filemap;
50 mod visitor;
51 mod checkstyle;
52 mod items;
53 mod missed_spans;
54 mod lists;
55 mod types;
56 mod expr;
57 mod imports;
58 mod issues;
59 mod rewrite;
60 mod string;
61 mod comment;
62 mod modules;
63 pub mod rustfmt_diff;
64 mod chains;
65 mod macros;
66 mod patterns;
67
68 const MIN_STRING: usize = 10;
69 // When we get scoped annotations, we should have rustfmt::skip.
70 const SKIP_ANNOTATION: &'static str = "rustfmt_skip";
71
72 pub trait Spanned {
73     fn span(&self) -> Span;
74 }
75
76 impl Spanned for ast::Expr {
77     fn span(&self) -> Span {
78         self.span
79     }
80 }
81
82 impl Spanned for ast::Pat {
83     fn span(&self) -> Span {
84         self.span
85     }
86 }
87
88 impl Spanned for ast::Ty {
89     fn span(&self) -> Span {
90         self.span
91     }
92 }
93
94 impl Spanned for ast::Arg {
95     fn span(&self) -> Span {
96         if items::is_named_arg(self) {
97             mk_sp(self.pat.span.lo, self.ty.span.hi)
98         } else {
99             self.ty.span
100         }
101     }
102 }
103
104 #[derive(Copy, Clone, Debug)]
105 pub struct Indent {
106     // Width of the block indent, in characters. Must be a multiple of
107     // Config::tab_spaces.
108     pub block_indent: usize,
109     // Alignment in characters.
110     pub alignment: usize,
111 }
112
113 impl Indent {
114     pub fn new(block_indent: usize, alignment: usize) -> Indent {
115         Indent {
116             block_indent: block_indent,
117             alignment: alignment,
118         }
119     }
120
121     pub fn empty() -> Indent {
122         Indent::new(0, 0)
123     }
124
125     pub fn block_indent(mut self, config: &Config) -> Indent {
126         self.block_indent += config.tab_spaces;
127         self
128     }
129
130     pub fn block_unindent(mut self, config: &Config) -> Indent {
131         self.block_indent -= config.tab_spaces;
132         self
133     }
134
135     pub fn width(&self) -> usize {
136         self.block_indent + self.alignment
137     }
138
139     pub fn to_string(&self, config: &Config) -> String {
140         let (num_tabs, num_spaces) = if config.hard_tabs {
141             (self.block_indent / config.tab_spaces, self.alignment)
142         } else {
143             (0, self.block_indent + self.alignment)
144         };
145         let num_chars = num_tabs + num_spaces;
146         let mut indent = String::with_capacity(num_chars);
147         for _ in 0..num_tabs {
148             indent.push('\t')
149         }
150         for _ in 0..num_spaces {
151             indent.push(' ')
152         }
153         indent
154     }
155 }
156
157 impl Add for Indent {
158     type Output = Indent;
159
160     fn add(self, rhs: Indent) -> Indent {
161         Indent {
162             block_indent: self.block_indent + rhs.block_indent,
163             alignment: self.alignment + rhs.alignment,
164         }
165     }
166 }
167
168 impl Sub for Indent {
169     type Output = Indent;
170
171     fn sub(self, rhs: Indent) -> Indent {
172         Indent::new(self.block_indent - rhs.block_indent,
173                     self.alignment - rhs.alignment)
174     }
175 }
176
177 impl Add<usize> for Indent {
178     type Output = Indent;
179
180     fn add(self, rhs: usize) -> Indent {
181         Indent::new(self.block_indent, self.alignment + rhs)
182     }
183 }
184
185 impl Sub<usize> for Indent {
186     type Output = Indent;
187
188     fn sub(self, rhs: usize) -> Indent {
189         Indent::new(self.block_indent, self.alignment - rhs)
190     }
191 }
192
193 pub enum ErrorKind {
194     // Line has exceeded character limit
195     LineOverflow,
196     // Line ends in whitespace
197     TrailingWhitespace,
198     // TO-DO or FIX-ME item without an issue number
199     BadIssue(Issue),
200 }
201
202 impl fmt::Display for ErrorKind {
203     fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
204         match *self {
205             ErrorKind::LineOverflow => write!(fmt, "line exceeded maximum length"),
206             ErrorKind::TrailingWhitespace => write!(fmt, "left behind trailing whitespace"),
207             ErrorKind::BadIssue(issue) => write!(fmt, "found {}", issue),
208         }
209     }
210 }
211
212 // Formatting errors that are identified *after* rustfmt has run.
213 pub struct FormattingError {
214     line: u32,
215     kind: ErrorKind,
216 }
217
218 impl FormattingError {
219     fn msg_prefix(&self) -> &str {
220         match self.kind {
221             ErrorKind::LineOverflow |
222             ErrorKind::TrailingWhitespace => "Rustfmt failed at",
223             ErrorKind::BadIssue(_) => "WARNING:",
224         }
225     }
226
227     fn msg_suffix(&self) -> &str {
228         match self.kind {
229             ErrorKind::LineOverflow |
230             ErrorKind::TrailingWhitespace => "(sorry)",
231             ErrorKind::BadIssue(_) => "",
232         }
233     }
234 }
235
236 pub struct FormatReport {
237     // Maps stringified file paths to their associated formatting errors.
238     file_error_map: HashMap<String, Vec<FormattingError>>,
239 }
240
241 impl FormatReport {
242     pub fn warning_count(&self) -> usize {
243         self.file_error_map.iter().map(|(_, ref errors)| errors.len()).fold(0, |acc, x| acc + x)
244     }
245 }
246
247 impl fmt::Display for FormatReport {
248     // Prints all the formatting errors.
249     fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
250         for (file, errors) in &self.file_error_map {
251             for error in errors {
252                 try!(write!(fmt,
253                             "{} {}:{}: {} {}\n",
254                             error.msg_prefix(),
255                             file,
256                             error.line,
257                             error.kind,
258                             error.msg_suffix()));
259             }
260         }
261         Ok(())
262     }
263 }
264
265 // Formatting which depends on the AST.
266 fn fmt_ast(krate: &ast::Crate,
267            parse_session: &ParseSess,
268            main_file: &Path,
269            config: &Config)
270            -> FileMap {
271     let mut file_map = FileMap::new();
272     for (path, module) in modules::list_files(krate, parse_session.codemap()) {
273         if config.skip_children && path.as_path() != main_file {
274             continue;
275         }
276         let path = path.to_str().unwrap();
277         if config.verbose {
278             println!("Formatting {}", path);
279         }
280         let mut visitor = FmtVisitor::from_codemap(parse_session, config);
281         visitor.format_separate_mod(module);
282         file_map.insert(path.to_owned(), visitor.buffer);
283     }
284     file_map
285 }
286
287 // Formatting done on a char by char or line by line basis.
288 // TODO(#209) warn on bad license
289 // TODO(#20) other stuff for parity with make tidy
290 pub fn fmt_lines(file_map: &mut FileMap, config: &Config) -> FormatReport {
291     let mut truncate_todo = Vec::new();
292     let mut report = FormatReport { file_error_map: HashMap::new() };
293
294     // Iterate over the chars in the file map.
295     for (f, text) in file_map.iter() {
296         let mut trims = vec![];
297         let mut last_wspace: Option<usize> = None;
298         let mut line_len = 0;
299         let mut cur_line = 1;
300         let mut newline_count = 0;
301         let mut errors = vec![];
302         let mut issue_seeker = BadIssueSeeker::new(config.report_todo, config.report_fixme);
303
304         for (c, b) in text.chars() {
305             if c == '\r' {
306                 line_len += c.len_utf8();
307                 continue;
308             }
309
310             // Add warnings for bad todos/ fixmes
311             if let Some(issue) = issue_seeker.inspect(c) {
312                 errors.push(FormattingError {
313                     line: cur_line,
314                     kind: ErrorKind::BadIssue(issue),
315                 });
316             }
317
318             if c == '\n' {
319                 // Check for (and record) trailing whitespace.
320                 if let Some(lw) = last_wspace {
321                     trims.push((cur_line, lw, b));
322                     line_len -= b - lw;
323                 }
324                 // Check for any line width errors we couldn't correct.
325                 if line_len > config.max_width {
326                     errors.push(FormattingError {
327                         line: cur_line,
328                         kind: ErrorKind::LineOverflow,
329                     });
330                 }
331                 line_len = 0;
332                 cur_line += 1;
333                 newline_count += 1;
334                 last_wspace = None;
335             } else {
336                 newline_count = 0;
337                 line_len += c.len_utf8();
338                 if c.is_whitespace() {
339                     if last_wspace.is_none() {
340                         last_wspace = Some(b);
341                     }
342                 } else {
343                     last_wspace = None;
344                 }
345             }
346         }
347
348         if newline_count > 1 {
349             debug!("track truncate: {} {} {}", f, text.len, newline_count);
350             truncate_todo.push((f.to_owned(), text.len - newline_count + 1))
351         }
352
353         for &(l, _, _) in &trims {
354             errors.push(FormattingError {
355                 line: l,
356                 kind: ErrorKind::TrailingWhitespace,
357             });
358         }
359
360         report.file_error_map.insert(f.to_owned(), errors);
361     }
362
363     for (f, l) in truncate_todo {
364         file_map.get_mut(&f).unwrap().truncate(l);
365     }
366
367     report
368 }
369
370 pub fn format_string(input: String, config: &Config) -> FileMap {
371     let path = "stdin";
372     let codemap = Rc::new(CodeMap::new());
373
374     let tty_handler = Handler::with_tty_emitter(ColorConfig::Auto,
375                                                 None,
376                                                 true,
377                                                 false,
378                                                 codemap.clone());
379     let mut parse_session = ParseSess::with_span_handler(tty_handler, codemap.clone());
380
381     let krate = parse::parse_crate_from_source_str(path.to_owned(),
382                                                    input,
383                                                    Vec::new(),
384                                                    &parse_session)
385                     .unwrap();
386
387     // Suppress error output after parsing.
388     let silent_emitter = Box::new(EmitterWriter::new(Box::new(Vec::new()), None, codemap.clone()));
389     parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter);
390
391     // FIXME: we still use a FileMap even though we only have
392     // one file, because fmt_lines requires a FileMap
393     let mut file_map = FileMap::new();
394
395     // do the actual formatting
396     let mut visitor = FmtVisitor::from_codemap(&parse_session, config);
397     visitor.format_separate_mod(&krate.module);
398
399     // append final newline
400     visitor.buffer.push_str("\n");
401     file_map.insert(path.to_owned(), visitor.buffer);
402
403     file_map
404 }
405
406 pub fn format(file: &Path, config: &Config) -> FileMap {
407     let codemap = Rc::new(CodeMap::new());
408
409     let tty_handler = Handler::with_tty_emitter(ColorConfig::Auto,
410                                                 None,
411                                                 true,
412                                                 false,
413                                                 codemap.clone());
414     let mut parse_session = ParseSess::with_span_handler(tty_handler, codemap.clone());
415
416     let krate = parse::parse_crate_from_file(file, Vec::new(), &parse_session).unwrap();
417
418     // Suppress error output after parsing.
419     let silent_emitter = Box::new(EmitterWriter::new(Box::new(Vec::new()), None, codemap.clone()));
420     parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter);
421
422     let mut file_map = fmt_ast(&krate, &parse_session, file, config);
423
424     // For some reason, the codemap does not include terminating
425     // newlines so we must add one on for each file. This is sad.
426     filemap::append_newlines(&mut file_map);
427
428     file_map
429 }
430
431 pub fn run(file: &Path, config: &Config) {
432     let mut result = format(file, config);
433
434     print!("{}", fmt_lines(&mut result, config));
435     let out = stdout();
436     let write_result = filemap::write_all_files(&result, out, config);
437
438     if let Err(msg) = write_result {
439         println!("Error writing files: {}", msg);
440     }
441 }
442
443 // Similar to run, but takes an input String instead of a file to format
444 pub fn run_from_stdin(input: String, config: &Config) {
445     let mut result = format_string(input, config);
446     fmt_lines(&mut result, config);
447
448     let mut out = stdout();
449     let write_result = filemap::write_file(&result["stdin"], "stdin", &mut out, config);
450
451     if let Err(msg) = write_result {
452         panic!("Error writing to stdout: {}", msg);
453     }
454 }