]> git.lizzy.rs Git - rust.git/blob - src/tools/tidy/src/features.rs
e1fdc19c27d2585a902cd19bfcd203707fd809de
[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         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                 println!("{}:{}: {}", file.display(), i + 1, msg);
81                 *bad = true;
82             };
83
84             let gate_test_str = "gate-test-";
85
86             if !line.contains(gate_test_str) {
87                 continue;
88             }
89
90             let feature_name = match line.find(gate_test_str) {
91                 Some(i) => {
92                     &line[i+gate_test_str.len()..line[i+1..].find(' ').unwrap_or(line.len())]
93                 },
94                 None => continue,
95             };
96             let found_feature = features.get_mut(feature_name)
97                                         .map(|v| { v.has_gate_test = true; () })
98                                         .is_some();
99
100             let found_lib_feature = features.get_mut(feature_name)
101                                             .map(|v| { v.has_gate_test = true; () })
102                                             .is_some();
103
104             if !(found_feature || found_lib_feature) {
105                 err(&format!("gate-test test found referencing a nonexistent feature '{}'",
106                              feature_name));
107             }
108         }
109     });
110
111     // Only check the number of lang features.
112     // Obligatory testing for library features is dumb.
113     let gate_untested = features.iter()
114                                 .filter(|&(_, f)| f.level == Status::Unstable)
115                                 .filter(|&(_, f)| !f.has_gate_test)
116                                 .collect::<Vec<_>>();
117
118     for &(name, _) in gate_untested.iter() {
119         println!("Expected a gate test for the feature '{}'.", name);
120         println!("Hint: create a file named 'feature-gate-{}.rs' in the compile-fail\
121                 \n      test suite, with its failures due to missing usage of\
122                 \n      #![feature({})].", name, name);
123         println!("Hint: If you already have such a test and don't want to rename it,\
124                 \n      you can also add a // gate-test-{} line to the test file.",
125                  name);
126     }
127
128     if gate_untested.len() > 0 {
129         println!("Found {} features without a gate test.", gate_untested.len());
130         *bad = true;
131     }
132
133     if *bad {
134         return;
135     }
136
137     let mut lines = Vec::new();
138     for (name, feature) in features.iter() {
139         lines.push(format!("{:<32} {:<8} {:<12} {:<8}",
140                            name,
141                            "lang",
142                            feature.level,
143                            feature.since));
144     }
145     for (name, feature) in lib_features {
146         lines.push(format!("{:<32} {:<8} {:<12} {:<8}",
147                            name,
148                            "lib",
149                            feature.level,
150                            feature.since));
151     }
152
153     lines.sort();
154     for line in lines {
155         println!("* {}", line);
156     }
157 }
158
159 fn find_attr_val<'a>(line: &'a str, attr: &str) -> Option<&'a str> {
160     line.find(attr)
161         .and_then(|i| line[i..].find('"').map(|j| i + j + 1))
162         .and_then(|i| line[i..].find('"').map(|j| (i, i + j)))
163         .map(|(i, j)| &line[i..j])
164 }
165
166 fn test_filen_gate(filen_underscore: &str,
167                    features: &mut HashMap<String, Feature>) -> bool {
168     if filen_underscore.starts_with("feature_gate") {
169         for (n, f) in features.iter_mut() {
170             if filen_underscore == format!("feature_gate_{}", n) {
171                 f.has_gate_test = true;
172                 return true;
173             }
174         }
175     }
176     return false;
177 }
178
179 pub fn collect_lang_features(base_src_path: &Path) -> HashMap<String, Feature> {
180     let mut contents = String::new();
181     let path = base_src_path.join("libsyntax/feature_gate.rs");
182     t!(t!(File::open(path)).read_to_string(&mut contents));
183
184     contents.lines()
185         .filter_map(|line| {
186             let mut parts = line.trim().split(",");
187             let level = match parts.next().map(|l| l.trim().trim_left_matches('(')) {
188                 Some("active") => Status::Unstable,
189                 Some("removed") => Status::Removed,
190                 Some("accepted") => Status::Stable,
191                 _ => return None,
192             };
193             let name = parts.next().unwrap().trim();
194             let since = parts.next().unwrap().trim().trim_matches('"');
195             Some((name.to_owned(),
196                 Feature {
197                     level: level,
198                     since: since.to_owned(),
199                     has_gate_test: false,
200                 }))
201         })
202         .collect()
203 }
204
205 pub fn collect_lib_features(base_src_path: &Path,
206                             bad: &mut bool,
207                             features: &HashMap<String, Feature>) -> HashMap<String, Feature> {
208     let mut lib_features = HashMap::<String, Feature>::new();
209     let mut contents = String::new();
210     super::walk(base_src_path,
211                 &mut |path| super::filter_dirs(path) || path.ends_with("src/test"),
212                 &mut |file| {
213         let filename = file.file_name().unwrap().to_string_lossy();
214         if !filename.ends_with(".rs") || filename == "features.rs" ||
215            filename == "diagnostic_list.rs" {
216             return;
217         }
218
219         contents.truncate(0);
220         t!(t!(File::open(&file), &file).read_to_string(&mut contents));
221
222         for (i, line) in contents.lines().enumerate() {
223             let mut err = |msg: &str| {
224                 println!("{}:{}: {}", file.display(), i + 1, msg);
225                 *bad = true;
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 }