]> git.lizzy.rs Git - rust.git/blob - src/tools/tidy/src/features.rs
Add [[T]] -> [T] examples to SliceConcatExt docs
[rust.git] / src / tools / tidy / src / features.rs
1 // Copyright 2015 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 ensure that unstable features are all in order
12 //!
13 //! This check will ensure properties like:
14 //!
15 //! * All stability attributes look reasonably well formed
16 //! * The set of library features is disjoint from the set of language features
17 //! * Library features have at most one stability level
18 //! * Library features have at most one `since` value
19 //! * All unstable lang features have tests to ensure they are actually unstable
20
21 use std::collections::HashMap;
22 use std::fmt;
23 use std::fs::File;
24 use std::io::prelude::*;
25 use std::path::Path;
26
27 #[derive(Debug, PartialEq)]
28 pub enum Status {
29     Stable,
30     Removed,
31     Unstable,
32 }
33
34 impl fmt::Display for Status {
35     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36         let as_str = match *self {
37             Status::Stable => "stable",
38             Status::Unstable => "unstable",
39             Status::Removed => "removed",
40         };
41         fmt::Display::fmt(as_str, f)
42     }
43 }
44
45 #[derive(Debug)]
46 pub struct Feature {
47     pub level: Status,
48     pub since: String,
49     pub has_gate_test: bool,
50 }
51
52 pub fn check(path: &Path, bad: &mut bool) {
53     let mut features = collect_lang_features(path);
54     assert!(!features.is_empty());
55
56     let lib_features = collect_lib_features(path, bad, &features);
57     assert!(!lib_features.is_empty());
58
59     let mut contents = String::new();
60
61     super::walk_many(&[&path.join("test/compile-fail"),
62                        &path.join("test/compile-fail-fulldeps"),
63                        &path.join("test/parse-fail"),],
64                      &mut |path| super::filter_dirs(path),
65                      &mut |file| {
66         let filename = file.file_name().unwrap().to_string_lossy();
67         if !filename.ends_with(".rs") || filename == "features.rs" ||
68            filename == "diagnostic_list.rs" {
69             return;
70         }
71
72         let filen_underscore = filename.replace("-","_").replace(".rs","");
73         let filename_is_gate_test = test_filen_gate(&filen_underscore, &mut features);
74
75         contents.truncate(0);
76         t!(t!(File::open(&file), &file).read_to_string(&mut contents));
77
78         for (i, line) in contents.lines().enumerate() {
79             let mut err = |msg: &str| {
80                 tidy_error!(bad, "{}:{}: {}", file.display(), i + 1, msg);
81             };
82
83             let gate_test_str = "gate-test-";
84
85             if !line.contains(gate_test_str) {
86                 continue;
87             }
88
89             let feature_name = match line.find(gate_test_str) {
90                 Some(i) => {
91                     &line[i+gate_test_str.len()..line[i+1..].find(' ').unwrap_or(line.len())]
92                 },
93                 None => continue,
94             };
95             match features.get_mut(feature_name) {
96                 Some(f) => {
97                     if filename_is_gate_test {
98                         err(&format!("The file is already marked as gate test \
99                                       through its name, no need for a \
100                                       'gate-test-{}' comment",
101                                      feature_name));
102                     }
103                     f.has_gate_test = true;
104                 }
105                 None => {
106                     err(&format!("gate-test test found referencing a nonexistent feature '{}'",
107                                  feature_name));
108                 }
109             }
110         }
111     });
112
113     // Only check the number of lang features.
114     // Obligatory testing for library features is dumb.
115     let gate_untested = features.iter()
116                                 .filter(|&(_, f)| f.level == Status::Unstable)
117                                 .filter(|&(_, f)| !f.has_gate_test)
118                                 .collect::<Vec<_>>();
119
120     for &(name, _) in gate_untested.iter() {
121         println!("Expected a gate test for the feature '{}'.", name);
122         println!("Hint: create a file named 'feature-gate-{}.rs' in the compile-fail\
123                 \n      test suite, with its failures due to missing usage of\
124                 \n      #![feature({})].", name, name);
125         println!("Hint: If you already have such a test and don't want to rename it,\
126                 \n      you can also add a // gate-test-{} line to the test file.",
127                  name);
128     }
129
130     if gate_untested.len() > 0 {
131         tidy_error!(bad, "Found {} features without a gate test.", gate_untested.len());
132     }
133
134     if *bad {
135         return;
136     }
137
138     let mut lines = Vec::new();
139     for (name, feature) in features.iter() {
140         lines.push(format!("{:<32} {:<8} {:<12} {:<8}",
141                            name,
142                            "lang",
143                            feature.level,
144                            feature.since));
145     }
146     for (name, feature) in lib_features {
147         lines.push(format!("{:<32} {:<8} {:<12} {:<8}",
148                            name,
149                            "lib",
150                            feature.level,
151                            feature.since));
152     }
153
154     lines.sort();
155     for line in lines {
156         println!("* {}", line);
157     }
158 }
159
160 fn find_attr_val<'a>(line: &'a str, attr: &str) -> Option<&'a str> {
161     line.find(attr)
162         .and_then(|i| line[i..].find('"').map(|j| i + j + 1))
163         .and_then(|i| line[i..].find('"').map(|j| (i, i + j)))
164         .map(|(i, j)| &line[i..j])
165 }
166
167 fn test_filen_gate(filen_underscore: &str,
168                    features: &mut HashMap<String, Feature>) -> bool {
169     if filen_underscore.starts_with("feature_gate") {
170         for (n, f) in features.iter_mut() {
171             if filen_underscore == format!("feature_gate_{}", n) {
172                 f.has_gate_test = true;
173                 return true;
174             }
175         }
176     }
177     return false;
178 }
179
180 pub fn collect_lang_features(base_src_path: &Path) -> HashMap<String, Feature> {
181     let mut contents = String::new();
182     let path = base_src_path.join("libsyntax/feature_gate.rs");
183     t!(t!(File::open(path)).read_to_string(&mut contents));
184
185     contents.lines()
186         .filter_map(|line| {
187             let mut parts = line.trim().split(",");
188             let level = match parts.next().map(|l| l.trim().trim_left_matches('(')) {
189                 Some("active") => Status::Unstable,
190                 Some("removed") => Status::Removed,
191                 Some("accepted") => Status::Stable,
192                 _ => return None,
193             };
194             let name = parts.next().unwrap().trim();
195             let since = parts.next().unwrap().trim().trim_matches('"');
196             Some((name.to_owned(),
197                 Feature {
198                     level: level,
199                     since: since.to_owned(),
200                     has_gate_test: false,
201                 }))
202         })
203         .collect()
204 }
205
206 pub fn collect_lib_features(base_src_path: &Path,
207                             bad: &mut bool,
208                             features: &HashMap<String, Feature>) -> HashMap<String, Feature> {
209     let mut lib_features = HashMap::<String, Feature>::new();
210     let mut contents = String::new();
211     super::walk(base_src_path,
212                 &mut |path| super::filter_dirs(path) || path.ends_with("src/test"),
213                 &mut |file| {
214         let filename = file.file_name().unwrap().to_string_lossy();
215         if !filename.ends_with(".rs") || filename == "features.rs" ||
216            filename == "diagnostic_list.rs" {
217             return;
218         }
219
220         contents.truncate(0);
221         t!(t!(File::open(&file), &file).read_to_string(&mut contents));
222
223         for (i, line) in contents.lines().enumerate() {
224             let mut err = |msg: &str| {
225                 tidy_error!(bad, "{}:{}: {}", file.display(), i + 1, msg);
226             };
227             let level = if line.contains("[unstable(") {
228                 Status::Unstable
229             } else if line.contains("[stable(") {
230                 Status::Stable
231             } else {
232                 continue;
233             };
234             let feature_name = match find_attr_val(line, "feature") {
235                 Some(name) => name,
236                 None => {
237                     err("malformed stability attribute");
238                     continue;
239                 }
240             };
241             let since = match find_attr_val(line, "since") {
242                 Some(name) => name,
243                 None if level == Status::Stable => {
244                     err("malformed stability attribute");
245                     continue;
246                 }
247                 None => "None",
248             };
249
250             if features.contains_key(feature_name) {
251                 err("duplicating a lang feature");
252             }
253             if let Some(ref s) = lib_features.get(feature_name) {
254                 if s.level != level {
255                     err("different stability level than before");
256                 }
257                 if s.since != since {
258                     err("different `since` than before");
259                 }
260                 continue;
261             }
262             lib_features.insert(feature_name.to_owned(),
263                                 Feature {
264                                     level: level,
265                                     since: since.to_owned(),
266                                     has_gate_test: false,
267                                 });
268         }
269     });
270     lib_features
271 }