]> git.lizzy.rs Git - rust.git/blob - src/tools/tidy/src/style.rs
b373725617c2b5c7fb8fd38dc2739dcb1db23b4d
[rust.git] / src / tools / tidy / src / style.rs
1 //! Tidy check to enforce various stylistic guidelines on the Rust codebase.
2 //!
3 //! Example checks are:
4 //!
5 //! * No lines over 100 characters.
6 //! * No tabs.
7 //! * No trailing whitespace.
8 //! * No CR characters.
9 //! * No `TODO` or `XXX` directives.
10 //! * No unexplained ` ```ignore ` or ` ```rust,ignore ` doc tests.
11 //!
12 //! A number of these checks can be opted-out of with various directives like
13 //! `// ignore-tidy-copyright`.
14
15 use std::fs::File;
16 use std::io::prelude::*;
17 use std::path::Path;
18
19 const COLS: usize = 100;
20
21 const UNEXPLAINED_IGNORE_DOCTEST_INFO: &str = r#"unexplained "```ignore" doctest; try one:
22
23 * make the test actually pass, by adding necessary imports and declarations, or
24 * use "```text", if the code is not Rust code, or
25 * use "```compile_fail,Ennnn", if the code is expected to fail at compile time, or
26 * use "```should_panic", if the code is expected to fail at run time, or
27 * use "```no_run", if the code should type-check but not necessary linkable/runnable, or
28 * explain it like "```ignore (cannot-test-this-because-xxxx)", if the annotation cannot be avoided.
29
30 "#;
31
32 const LLVM_UNREACHABLE_INFO: &str = r"\
33 C++ code used llvm_unreachable, which triggers undefined behavior
34 when executed when assertions are disabled.
35 Use llvm::report_fatal_error for increased robustness.";
36
37 /// Parser states for `line_is_url`.
38 #[derive(PartialEq)]
39 #[allow(non_camel_case_types)]
40 enum LIUState {
41     EXP_COMMENT_START,
42     EXP_LINK_LABEL_OR_URL,
43     EXP_URL,
44     EXP_END,
45 }
46
47 /// Returns `true` if `line` appears to be a line comment containing an URL,
48 /// possibly with a Markdown link label in front, and nothing else.
49 /// The Markdown link label, if present, may not contain whitespace.
50 /// Lines of this form are allowed to be overlength, because Markdown
51 /// offers no way to split a line in the middle of a URL, and the lengths
52 /// of URLs to external references are beyond our control.
53 fn line_is_url(line: &str) -> bool {
54     use self::LIUState::*;
55     let mut state: LIUState = EXP_COMMENT_START;
56
57     for tok in line.split_whitespace() {
58         match (state, tok) {
59             (EXP_COMMENT_START, "//") => state = EXP_LINK_LABEL_OR_URL,
60             (EXP_COMMENT_START, "///") => state = EXP_LINK_LABEL_OR_URL,
61             (EXP_COMMENT_START, "//!") => state = EXP_LINK_LABEL_OR_URL,
62
63             (EXP_LINK_LABEL_OR_URL, w)
64                 if w.len() >= 4 && w.starts_with('[') && w.ends_with("]:")
65                 => state = EXP_URL,
66
67             (EXP_LINK_LABEL_OR_URL, w)
68                 if w.starts_with("http://") || w.starts_with("https://")
69                 => state = EXP_END,
70
71             (EXP_URL, w)
72                 if w.starts_with("http://") || w.starts_with("https://") || w.starts_with("../")
73                 => state = EXP_END,
74
75             (_, _) => return false,
76         }
77     }
78
79     state == EXP_END
80 }
81
82 /// Returns `true` if `line` is allowed to be longer than the normal limit.
83 /// Currently there is only one exception, for long URLs, but more
84 /// may be added in the future.
85 fn long_line_is_ok(line: &str) -> bool {
86     if line_is_url(line) {
87         return true;
88     }
89
90     false
91 }
92
93 fn contains_ignore_directive(contents: &String, check: &str) -> bool {
94     contents.contains(&format!("// ignore-tidy-{}", check)) ||
95         contents.contains(&format!("# ignore-tidy-{}", check))
96 }
97
98 pub fn check(path: &Path, bad: &mut bool) {
99     let mut contents = String::new();
100     super::walk(path, &mut super::filter_dirs, &mut |file| {
101         let filename = file.file_name().unwrap().to_string_lossy();
102         let extensions = [".rs", ".py", ".js", ".sh", ".c", ".cpp", ".h"];
103         if extensions.iter().all(|e| !filename.ends_with(e)) ||
104            filename.starts_with(".#") {
105             return
106         }
107
108         contents.truncate(0);
109         t!(t!(File::open(file), file).read_to_string(&mut contents));
110
111         if contents.is_empty() {
112             tidy_error!(bad, "{}: empty file", file.display());
113         }
114
115         let skip_cr = contains_ignore_directive(&contents, "cr");
116         let skip_tab = contains_ignore_directive(&contents, "tab");
117         let skip_length = contains_ignore_directive(&contents, "linelength");
118         let skip_end_whitespace = contains_ignore_directive(&contents, "end-whitespace");
119         let skip_copyright = contains_ignore_directive(&contents, "copyright");
120         let mut leading_new_lines = false;
121         let mut trailing_new_lines = 0;
122         let mut contained_long_line = false;
123         for (i, line) in contents.split('\n').enumerate() {
124             let mut err = |msg: &str| {
125                 tidy_error!(bad, "{}:{}: {}", file.display(), i + 1, msg);
126             };
127             if line.chars().count() > COLS && !long_line_is_ok(line) {
128                 contained_long_line = true;
129                 if !skip_length {
130                     err(&format!("line longer than {} chars", COLS));
131                 }
132             }
133             if !skip_tab && line.contains('\t') {
134                 err("tab character");
135             }
136             if !skip_end_whitespace && (line.ends_with(' ') || line.ends_with('\t')) {
137                 err("trailing whitespace");
138             }
139             if !skip_cr && line.contains('\r') {
140                 err("CR character");
141             }
142             if filename != "style.rs" {
143                 if line.contains("TODO") {
144                     err("TODO is deprecated; use FIXME")
145                 }
146                 if line.contains("//") && line.contains(" XXX") {
147                     err("XXX is deprecated; use FIXME")
148                 }
149             }
150             if !skip_copyright && (line.starts_with("// Copyright") ||
151                                    line.starts_with("# Copyright") ||
152                                    line.starts_with("Copyright"))
153                                && (line.contains("Rust Developers") ||
154                                    line.contains("Rust Project Developers")) {
155                 err("copyright notices attributed to the Rust Project Developers are deprecated");
156             }
157             if line.ends_with("```ignore") || line.ends_with("```rust,ignore") {
158                 err(UNEXPLAINED_IGNORE_DOCTEST_INFO);
159             }
160             if filename.ends_with(".cpp") && line.contains("llvm_unreachable") {
161                 err(LLVM_UNREACHABLE_INFO);
162             }
163             if line.is_empty() {
164                 if i == 0 {
165                     leading_new_lines = true;
166                 }
167                 trailing_new_lines += 1;
168             } else {
169                 trailing_new_lines = 0;
170             }
171         }
172         if leading_new_lines {
173             tidy_error!(bad, "{}: leading newline", file.display());
174         }
175         match trailing_new_lines {
176             0 => tidy_error!(bad, "{}: missing trailing newline", file.display()),
177             1 => {}
178             n => tidy_error!(bad, "{}: too many trailing newlines ({})", file.display(), n),
179         };
180         if !contained_long_line && skip_length {
181             tidy_error!(bad, "{}: ignoring line length unnecessarily", file.display());
182         }
183     })
184 }