]> git.proxmox.com Git - rustc.git/blob - src/tools/tidy/src/style.rs
Update upstream source from tag 'upstream/1.24.1+dfsg1'
[rustc.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 //! * A valid license header is at the top
21 //! * No unexplained ` ```ignore ` or ` ```rust,ignore ` doc tests
22 //!
23 //! A number of these checks can be opted-out of with various directives like
24 //! `// ignore-tidy-linelength`.
25
26 use std::fs::File;
27 use std::io::prelude::*;
28 use std::path::Path;
29
30 const COLS: usize = 100;
31 const LICENSE: &'static str = "\
32 Copyright <year> The Rust Project Developers. See the COPYRIGHT
33 file at the top-level directory of this distribution and at
34 http://rust-lang.org/COPYRIGHT.
35
36 Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
37 http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
38 <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
39 option. This file may not be copied, modified, or distributed
40 except according to those terms.";
41
42 const UNEXPLAINED_IGNORE_DOCTEST_INFO: &str = r#"unexplained "```ignore" doctest; try one:
43
44 * make the test actually pass, by adding necessary imports and declarations, or
45 * use "```text", if the code is not Rust code, or
46 * use "```compile_fail,Ennnn", if the code is expected to fail at compile time, or
47 * use "```should_panic", if the code is expected to fail at run time, or
48 * use "```no_run", if the code should type-check but not necessary linkable/runnable, or
49 * explain it like "```ignore (cannot-test-this-because-xxxx)", if the annotation cannot be avoided.
50
51 "#;
52
53 const LLVM_UNREACHABLE_INFO: &str = r"\
54 C++ code used llvm_unreachable, which triggers undefined behavior
55 when executed when assertions are disabled.
56 Use llvm::report_fatal_error for increased robustness.";
57
58 /// Parser states for line_is_url.
59 #[derive(PartialEq)]
60 #[allow(non_camel_case_types)]
61 enum LIUState { EXP_COMMENT_START,
62 EXP_LINK_LABEL_OR_URL,
63 EXP_URL,
64 EXP_END }
65
66 /// True if LINE appears to be a line comment containing an URL,
67 /// possibly with a Markdown link label in front, and nothing else.
68 /// The Markdown link label, if present, may not contain whitespace.
69 /// Lines of this form are allowed to be overlength, because Markdown
70 /// offers no way to split a line in the middle of a URL, and the lengths
71 /// of URLs to external references are beyond our control.
72 fn line_is_url(line: &str) -> bool {
73 use self::LIUState::*;
74 let mut state: LIUState = EXP_COMMENT_START;
75
76 for tok in line.split_whitespace() {
77 match (state, tok) {
78 (EXP_COMMENT_START, "//") => state = EXP_LINK_LABEL_OR_URL,
79 (EXP_COMMENT_START, "///") => state = EXP_LINK_LABEL_OR_URL,
80 (EXP_COMMENT_START, "//!") => state = EXP_LINK_LABEL_OR_URL,
81
82 (EXP_LINK_LABEL_OR_URL, w)
83 if w.len() >= 4 && w.starts_with("[") && w.ends_with("]:")
84 => state = EXP_URL,
85
86 (EXP_LINK_LABEL_OR_URL, w)
87 if w.starts_with("http://") || w.starts_with("https://")
88 => state = EXP_END,
89
90 (EXP_URL, w)
91 if w.starts_with("http://") || w.starts_with("https://") || w.starts_with("../")
92 => state = EXP_END,
93
94 (_, _) => return false,
95 }
96 }
97
98 state == EXP_END
99 }
100
101 /// True if LINE is allowed to be longer than the normal limit.
102 /// Currently there is only one exception, for long URLs, but more
103 /// may be added in the future.
104 fn long_line_is_ok(line: &str) -> bool {
105 if line_is_url(line) {
106 return true;
107 }
108
109 false
110 }
111
112 pub fn check(path: &Path, bad: &mut bool) {
113 let mut contents = String::new();
114 super::walk(path, &mut super::filter_dirs, &mut |file| {
115 let filename = file.file_name().unwrap().to_string_lossy();
116 let extensions = [".rs", ".py", ".js", ".sh", ".c", ".cpp", ".h"];
117 if extensions.iter().all(|e| !filename.ends_with(e)) ||
118 filename.starts_with(".#") {
119 return
120 }
121
122 contents.truncate(0);
123 t!(t!(File::open(file), file).read_to_string(&mut contents));
124
125 if contents.is_empty() {
126 tidy_error!(bad, "{}: empty file", file.display());
127 }
128
129 let skip_cr = contents.contains("ignore-tidy-cr");
130 let skip_tab = contents.contains("ignore-tidy-tab");
131 let skip_length = contents.contains("ignore-tidy-linelength");
132 let skip_end_whitespace = contents.contains("ignore-tidy-end-whitespace");
133 let mut trailing_new_lines = 0;
134 for (i, line) in contents.split("\n").enumerate() {
135 let mut err = |msg: &str| {
136 tidy_error!(bad, "{}:{}: {}", file.display(), i + 1, msg);
137 };
138 if !skip_length && line.chars().count() > COLS
139 && !long_line_is_ok(line) {
140 err(&format!("line longer than {} chars", COLS));
141 }
142 if line.contains("\t") && !skip_tab {
143 err("tab character");
144 }
145 if !skip_end_whitespace && (line.ends_with(" ") || line.ends_with("\t")) {
146 err("trailing whitespace");
147 }
148 if line.contains("\r") && !skip_cr {
149 err("CR character");
150 }
151 if filename != "style.rs" {
152 if line.contains("TODO") {
153 err("TODO is deprecated; use FIXME")
154 }
155 if line.contains("//") && line.contains(" XXX") {
156 err("XXX is deprecated; use FIXME")
157 }
158 }
159 if line.ends_with("```ignore") || line.ends_with("```rust,ignore") {
160 err(UNEXPLAINED_IGNORE_DOCTEST_INFO);
161 }
162 if filename.ends_with(".cpp") && line.contains("llvm_unreachable") {
163 err(LLVM_UNREACHABLE_INFO);
164 }
165 if line.is_empty() {
166 trailing_new_lines += 1;
167 } else {
168 trailing_new_lines = 0;
169 }
170 }
171 if !licenseck(file, &contents) {
172 tidy_error!(bad, "{}: incorrect license", file.display());
173 }
174 match trailing_new_lines {
175 0 => tidy_error!(bad, "{}: missing trailing newline", file.display()),
176 1 | 2 => {}
177 n => tidy_error!(bad, "{}: too many trailing newlines ({})", file.display(), n),
178 };
179 })
180 }
181
182 fn licenseck(file: &Path, contents: &str) -> bool {
183 if contents.contains("ignore-license") {
184 return true
185 }
186 let exceptions = [
187 "libstd/sync/mpsc/mpsc_queue.rs",
188 "libstd/sync/mpsc/spsc_queue.rs",
189 ];
190 if exceptions.iter().any(|f| file.ends_with(f)) {
191 return true
192 }
193
194 // Skip the BOM if it's there
195 let bom = "\u{feff}";
196 let contents = if contents.starts_with(bom) {&contents[3..]} else {contents};
197
198 // See if the license shows up in the first 100 lines
199 let lines = contents.lines().take(100).collect::<Vec<_>>();
200 lines.windows(LICENSE.lines().count()).any(|window| {
201 let offset = if window.iter().all(|w| w.starts_with("//")) {
202 2
203 } else if window.iter().all(|w| w.starts_with('#')) {
204 1
205 } else if window.iter().all(|w| w.starts_with(" *")) {
206 2
207 } else {
208 return false
209 };
210 window.iter().map(|a| a[offset..].trim())
211 .zip(LICENSE.lines()).all(|(a, b)| {
212 a == b || match b.find("<year>") {
213 Some(i) => a.starts_with(&b[..i]) && a.ends_with(&b[i+6..]),
214 None => false,
215 }
216 })
217 })
218
219 }