1 //! Tidy check to enforce various stylistic guidelines on the Rust codebase.
3 //! Example checks are:
5 //! * No lines over 100 characters.
6 //! * No files with over 3000 lines.
8 //! * No trailing whitespace.
9 //! * No CR characters.
10 //! * No `TODO` or `XXX` directives.
11 //! * No unexplained ` ```ignore ` or ` ```rust,ignore ` doc tests.
13 //! A number of these checks can be opted-out of with various directives of the form:
14 //! `// ignore-tidy-CHECK-NAME`.
17 use std::io::prelude::*;
20 const COLS: usize = 100;
22 const LINES: usize = 3000;
24 const UNEXPLAINED_IGNORE_DOCTEST_INFO: &str = r#"unexplained "```ignore" doctest; try one:
26 * make the test actually pass, by adding necessary imports and declarations, or
27 * use "```text", if the code is not Rust code, or
28 * use "```compile_fail,Ennnn", if the code is expected to fail at compile time, or
29 * use "```should_panic", if the code is expected to fail at run time, or
30 * use "```no_run", if the code should type-check but not necessary linkable/runnable, or
31 * explain it like "```ignore (cannot-test-this-because-xxxx)", if the annotation cannot be avoided.
35 const LLVM_UNREACHABLE_INFO: &str = r"\
36 C++ code used llvm_unreachable, which triggers undefined behavior
37 when executed when assertions are disabled.
38 Use llvm::report_fatal_error for increased robustness.";
40 /// Parser states for `line_is_url`.
42 #[allow(non_camel_case_types)]
45 EXP_LINK_LABEL_OR_URL,
50 /// Returns `true` if `line` appears to be a line comment containing an URL,
51 /// possibly with a Markdown link label in front, and nothing else.
52 /// The Markdown link label, if present, may not contain whitespace.
53 /// Lines of this form are allowed to be overlength, because Markdown
54 /// offers no way to split a line in the middle of a URL, and the lengths
55 /// of URLs to external references are beyond our control.
56 fn line_is_url(line: &str) -> bool {
57 use self::LIUState::*;
58 let mut state: LIUState = EXP_COMMENT_START;
60 for tok in line.split_whitespace() {
62 (EXP_COMMENT_START, "//") => state = EXP_LINK_LABEL_OR_URL,
63 (EXP_COMMENT_START, "///") => state = EXP_LINK_LABEL_OR_URL,
64 (EXP_COMMENT_START, "//!") => state = EXP_LINK_LABEL_OR_URL,
66 (EXP_LINK_LABEL_OR_URL, w)
67 if w.len() >= 4 && w.starts_with('[') && w.ends_with("]:")
70 (EXP_LINK_LABEL_OR_URL, w)
71 if w.starts_with("http://") || w.starts_with("https://")
75 if w.starts_with("http://") || w.starts_with("https://") || w.starts_with("../")
78 (_, _) => return false,
85 /// Returns `true` if `line` is allowed to be longer than the normal limit.
86 /// Currently there is only one exception, for long URLs, but more
87 /// may be added in the future.
88 fn long_line_is_ok(line: &str) -> bool {
89 if line_is_url(line) {
97 /// By default, tidy always warns against style issues.
100 /// `Ignore(false)` means that an `ignore-tidy-*` directive
101 /// has been provided, but is unnecessary. `Ignore(true)`
102 /// means that it is necessary (i.e. a warning would be
103 /// produced if `ignore-tidy-*` was not present).
107 fn contains_ignore_directive(contents: &String, check: &str) -> Directive {
108 if contents.contains(&format!("// ignore-tidy-{}", check)) ||
109 contents.contains(&format!("# ignore-tidy-{}", check)) {
110 Directive::Ignore(false)
116 macro_rules! suppressible_tidy_err {
117 ($err:ident, $skip:ident, $msg:expr) => {
118 if let Directive::Deny = $skip {
121 $skip = Directive::Ignore(true);
126 pub fn check(path: &Path, bad: &mut bool) {
127 let mut contents = String::new();
128 super::walk(path, &mut super::filter_dirs, &mut |file| {
129 let filename = file.file_name().unwrap().to_string_lossy();
130 let extensions = [".rs", ".py", ".js", ".sh", ".c", ".cpp", ".h"];
131 if extensions.iter().all(|e| !filename.ends_with(e)) ||
132 filename.starts_with(".#") {
136 contents.truncate(0);
137 t!(t!(File::open(file), file).read_to_string(&mut contents));
139 if contents.is_empty() {
140 tidy_error!(bad, "{}: empty file", file.display());
143 let mut skip_cr = contains_ignore_directive(&contents, "cr");
144 let mut skip_tab = contains_ignore_directive(&contents, "tab");
145 let mut skip_line_length = contains_ignore_directive(&contents, "linelength");
146 let mut skip_file_length = contains_ignore_directive(&contents, "filelength");
147 let mut skip_end_whitespace = contains_ignore_directive(&contents, "end-whitespace");
148 let mut skip_copyright = contains_ignore_directive(&contents, "copyright");
149 let mut leading_new_lines = false;
150 let mut trailing_new_lines = 0;
152 for (i, line) in contents.split('\n').enumerate() {
153 let mut err = |msg: &str| {
154 tidy_error!(bad, "{}:{}: {}", file.display(), i + 1, msg);
156 if line.chars().count() > COLS && !long_line_is_ok(line) {
157 suppressible_tidy_err!(
160 &format!("line longer than {} chars", COLS)
163 if line.contains('\t') {
164 suppressible_tidy_err!(err, skip_tab, "tab character");
166 if line.ends_with(' ') || line.ends_with('\t') {
167 suppressible_tidy_err!(err, skip_end_whitespace, "trailing whitespace");
169 if line.contains('\r') {
170 suppressible_tidy_err!(err, skip_cr, "CR character");
172 if filename != "style.rs" {
173 if line.contains("TODO") {
174 err("TODO is deprecated; use FIXME")
176 if line.contains("//") && line.contains(" XXX") {
177 err("XXX is deprecated; use FIXME")
180 if (line.starts_with("// Copyright") ||
181 line.starts_with("# Copyright") ||
182 line.starts_with("Copyright"))
183 && (line.contains("Rust Developers") ||
184 line.contains("Rust Project Developers")) {
185 suppressible_tidy_err!(
188 "copyright notices attributed to the Rust Project Developers are deprecated"
191 if line.ends_with("```ignore") || line.ends_with("```rust,ignore") {
192 err(UNEXPLAINED_IGNORE_DOCTEST_INFO);
194 if filename.ends_with(".cpp") && line.contains("llvm_unreachable") {
195 err(LLVM_UNREACHABLE_INFO);
199 leading_new_lines = true;
201 trailing_new_lines += 1;
203 trailing_new_lines = 0;
207 if leading_new_lines {
208 tidy_error!(bad, "{}: leading newline", file.display());
210 match trailing_new_lines {
211 0 => tidy_error!(bad, "{}: missing trailing newline", file.display()),
213 n => tidy_error!(bad, "{}: too many trailing newlines ({})", file.display(), n),
215 if !skip_file_length && lines > LINES {
216 tidy_error!(bad, "{}: too many lines ({})", file.display(), lines);
219 if let Directive::Ignore(false) = skip_cr {
220 tidy_error!(bad, "{}: ignoring CR characters unnecessarily", file.display());
222 if let Directive::Ignore(false) = skip_tab {
223 tidy_error!(bad, "{}: ignoring tab characters unnecessarily", file.display());
225 if let Directive::Ignore(false) = skip_line_length {
226 tidy_error!(bad, "{}: ignoring line length unnecessarily", file.display());
228 if let Directive::Ignore(false) = skip_file_length {
229 tidy_error!(bad, "{}: ignoring file length unnecessarily", file.display());
231 if let Directive::Ignore(false) = skip_end_whitespace {
232 tidy_error!(bad, "{}: ignoring trailing whitespace unnecessarily", file.display());
234 if let Directive::Ignore(false) = skip_copyright {
235 tidy_error!(bad, "{}: ignoring copyright unnecessarily", file.display());