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