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.
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.
11 //! Tidy check to enforce various stylistic guidelines on the Rust codebase.
13 //! Example checks are:
15 //! * No lines over 100 characters
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
23 //! A number of these checks can be opted-out of with various directives like
24 //! `// ignore-tidy-linelength`.
27 use std
::io
::prelude
::*;
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.
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.";
42 const UNEXPLAINED_IGNORE_DOCTEST_INFO
: &str = r
#"unexplained "```ignore" doctest; try one:
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.
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.";
58 /// Parser states for line_is_url.
60 #[allow(non_camel_case_types)]
61 enum LIUState
{ EXP_COMMENT_START
,
62 EXP_LINK_LABEL_OR_URL
,
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
;
76 for tok
in line
.split_whitespace() {
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
,
82 (EXP_LINK_LABEL_OR_URL
, w
)
83 if w
.len() >= 4 && w
.starts_with("[") && w
.ends_with("]:")
86 (EXP_LINK_LABEL_OR_URL
, w
)
87 if w
.starts_with("http://") || w
.starts_with("https://")
91 if w
.starts_with("http://") || w
.starts_with("https://") || w
.starts_with("../")
94 (_
, _
) => return false,
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
) {
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(".#") {
122 contents
.truncate(0);
123 t
!(t
!(File
::open(file
), file
).read_to_string(&mut contents
));
125 if contents
.is_empty() {
126 tidy_error
!(bad
, "{}: empty file", file
.display());
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
);
138 if !skip_length
&& line
.chars().count() > COLS
139 && !long_line_is_ok(line
) {
140 err(&format
!("line longer than {} chars", COLS
));
142 if line
.contains("\t") && !skip_tab
{
143 err("tab character");
145 if !skip_end_whitespace
&& (line
.ends_with(" ") || line
.ends_with("\t")) {
146 err("trailing whitespace");
148 if line
.contains("\r") && !skip_cr
{
151 if filename
!= "style.rs" {
152 if line
.contains("TODO") {
153 err("TODO is deprecated; use FIXME")
155 if line
.contains("//") && line
.contains(" XXX") {
156 err("XXX is deprecated; use FIXME")
159 if line
.ends_with("```ignore") || line
.ends_with("```rust,ignore") {
160 err(UNEXPLAINED_IGNORE_DOCTEST_INFO
);
162 if filename
.ends_with(".cpp") && line
.contains("llvm_unreachable") {
163 err(LLVM_UNREACHABLE_INFO
);
166 trailing_new_lines
+= 1;
168 trailing_new_lines
= 0;
171 if !licenseck(file
, &contents
) {
172 tidy_error
!(bad
, "{}: incorrect license", file
.display());
174 match trailing_new_lines
{
175 0 => tidy_error
!(bad
, "{}: missing trailing newline", file
.display()),
177 n
=> tidy_error
!(bad
, "{}: too many trailing newlines ({})", file
.display(), n
),
182 fn licenseck(file
: &Path
, contents
: &str) -> bool
{
183 if contents
.contains("ignore-license") {
187 "libstd/sync/mpsc/mpsc_queue.rs",
188 "libstd/sync/mpsc/spsc_queue.rs",
190 if exceptions
.iter().any(|f
| file
.ends_with(f
)) {
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}
;
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("//")) {
203 } else if window
.iter().all(|w
| w
.starts_with('
#')) {
205 } else if window
.iter().all(|w
| w
.starts_with(" *")) {
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..]),