]> git.proxmox.com Git - rustc.git/blob - src/tools/tidy/src/style.rs
Merge branch 'debian/sid' into debian/experimental
[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 /// Parser states for line_is_url.
54 #[derive(PartialEq)]
55 #[allow(non_camel_case_types)]
56 enum LIUState { EXP_COMMENT_START,
57 EXP_LINK_LABEL_OR_URL,
58 EXP_URL,
59 EXP_END }
60
61 /// True if LINE appears to be a line comment containing an URL,
62 /// possibly with a Markdown link label in front, and nothing else.
63 /// The Markdown link label, if present, may not contain whitespace.
64 /// Lines of this form are allowed to be overlength, because Markdown
65 /// offers no way to split a line in the middle of a URL, and the lengths
66 /// of URLs to external references are beyond our control.
67 fn line_is_url(line: &str) -> bool {
68 use self::LIUState::*;
69 let mut state: LIUState = EXP_COMMENT_START;
70
71 for tok in line.split_whitespace() {
72 match (state, tok) {
73 (EXP_COMMENT_START, "//") => state = EXP_LINK_LABEL_OR_URL,
74 (EXP_COMMENT_START, "///") => state = EXP_LINK_LABEL_OR_URL,
75 (EXP_COMMENT_START, "//!") => state = EXP_LINK_LABEL_OR_URL,
76
77 (EXP_LINK_LABEL_OR_URL, w)
78 if w.len() >= 4 && w.starts_with("[") && w.ends_with("]:")
79 => state = EXP_URL,
80
81 (EXP_LINK_LABEL_OR_URL, w)
82 if w.starts_with("http://") || w.starts_with("https://")
83 => state = EXP_END,
84
85 (EXP_URL, w)
86 if w.starts_with("http://") || w.starts_with("https://")
87 => state = EXP_END,
88
89 (_, _) => return false,
90 }
91 }
92
93 state == EXP_END
94 }
95
96 /// True if LINE is allowed to be longer than the normal limit.
97 /// Currently there is only one exception, for long URLs, but more
98 /// may be added in the future.
99 fn long_line_is_ok(line: &str) -> bool {
100 if line_is_url(line) {
101 return true;
102 }
103
104 false
105 }
106
107 pub fn check(path: &Path, bad: &mut bool) {
108 let mut contents = String::new();
109 super::walk(path, &mut super::filter_dirs, &mut |file| {
110 let filename = file.file_name().unwrap().to_string_lossy();
111 let extensions = [".rs", ".py", ".js", ".sh", ".c", ".h"];
112 if extensions.iter().all(|e| !filename.ends_with(e)) ||
113 filename.starts_with(".#") {
114 return
115 }
116
117 contents.truncate(0);
118 t!(t!(File::open(file), file).read_to_string(&mut contents));
119
120 if contents.is_empty() {
121 tidy_error!(bad, "{}: empty file", file.display());
122 }
123
124 let skip_cr = contents.contains("ignore-tidy-cr");
125 let skip_tab = contents.contains("ignore-tidy-tab");
126 let skip_length = contents.contains("ignore-tidy-linelength");
127 let skip_end_whitespace = contents.contains("ignore-tidy-end-whitespace");
128 for (i, line) in contents.split("\n").enumerate() {
129 let mut err = |msg: &str| {
130 tidy_error!(bad, "{}:{}: {}", file.display(), i + 1, msg);
131 };
132 if !skip_length && line.chars().count() > COLS
133 && !long_line_is_ok(line) {
134 err(&format!("line longer than {} chars", COLS));
135 }
136 if line.contains("\t") && !skip_tab {
137 err("tab character");
138 }
139 if !skip_end_whitespace && (line.ends_with(" ") || line.ends_with("\t")) {
140 err("trailing whitespace");
141 }
142 if line.contains("\r") && !skip_cr {
143 err("CR character");
144 }
145 if filename != "style.rs" {
146 if line.contains("TODO") {
147 err("TODO is deprecated; use FIXME")
148 }
149 if line.contains("//") && line.contains(" XXX") {
150 err("XXX is deprecated; use FIXME")
151 }
152 }
153 if line.ends_with("```ignore") || line.ends_with("```rust,ignore") {
154 err(UNEXPLAINED_IGNORE_DOCTEST_INFO);
155 }
156 }
157 if !licenseck(file, &contents) {
158 tidy_error!(bad, "{}: incorrect license", file.display());
159 }
160 })
161 }
162
163 fn licenseck(file: &Path, contents: &str) -> bool {
164 if contents.contains("ignore-license") {
165 return true
166 }
167 let exceptions = [
168 "libstd/sync/mpsc/mpsc_queue.rs",
169 "libstd/sync/mpsc/spsc_queue.rs",
170 ];
171 if exceptions.iter().any(|f| file.ends_with(f)) {
172 return true
173 }
174
175 // Skip the BOM if it's there
176 let bom = "\u{feff}";
177 let contents = if contents.starts_with(bom) {&contents[3..]} else {contents};
178
179 // See if the license shows up in the first 100 lines
180 let lines = contents.lines().take(100).collect::<Vec<_>>();
181 lines.windows(LICENSE.lines().count()).any(|window| {
182 let offset = if window.iter().all(|w| w.starts_with("//")) {
183 2
184 } else if window.iter().all(|w| w.starts_with('#')) {
185 1
186 } else if window.iter().all(|w| w.starts_with(" *")) {
187 2
188 } else {
189 return false
190 };
191 window.iter().map(|a| a[offset..].trim())
192 .zip(LICENSE.lines()).all(|(a, b)| {
193 a == b || match b.find("<year>") {
194 Some(i) => a.starts_with(&b[..i]) && a.ends_with(&b[i+6..]),
195 None => false,
196 }
197 })
198 })
199
200 }