]>
Commit | Line | Data |
---|---|---|
a7813a04 XL |
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 | |
32a655c1 | 19 | //! * All unstable lang features have tests to ensure they are actually unstable |
a7813a04 XL |
20 | |
21 | use std::collections::HashMap; | |
c30ab7b3 | 22 | use std::fmt; |
a7813a04 XL |
23 | use std::fs::File; |
24 | use std::io::prelude::*; | |
25 | use std::path::Path; | |
26 | ||
cc61c64b XL |
27 | #[derive(Debug, PartialEq)] |
28 | pub enum Status { | |
c30ab7b3 | 29 | Stable, |
32a655c1 | 30 | Removed, |
c30ab7b3 SL |
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", | |
32a655c1 | 39 | Status::Removed => "removed", |
c30ab7b3 SL |
40 | }; |
41 | fmt::Display::fmt(as_str, f) | |
42 | } | |
43 | } | |
44 | ||
cc61c64b XL |
45 | #[derive(Debug)] |
46 | pub struct Feature { | |
47 | pub level: Status, | |
48 | pub since: String, | |
49 | pub has_gate_test: bool, | |
a7813a04 XL |
50 | } |
51 | ||
7cac9316 | 52 | pub fn check(path: &Path, bad: &mut bool, quiet: bool) { |
cc61c64b | 53 | let mut features = collect_lang_features(path); |
c30ab7b3 | 54 | assert!(!features.is_empty()); |
a7813a04 | 55 | |
cc61c64b XL |
56 | let lib_features = collect_lib_features(path, bad, &features); |
57 | assert!(!lib_features.is_empty()); | |
a7813a04 | 58 | |
cc61c64b | 59 | let mut contents = String::new(); |
a7813a04 | 60 | |
32a655c1 | 61 | super::walk_many(&[&path.join("test/compile-fail"), |
8bb4bdeb XL |
62 | &path.join("test/compile-fail-fulldeps"), |
63 | &path.join("test/parse-fail"),], | |
32a655c1 SL |
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",""); | |
7cac9316 | 73 | let filename_is_gate_test = test_filen_gate(&filen_underscore, &mut features); |
32a655c1 SL |
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| { | |
cc61c64b | 80 | tidy_error!(bad, "{}:{}: {}", file.display(), i + 1, msg); |
32a655c1 SL |
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 | }; | |
7cac9316 XL |
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 | } | |
32a655c1 SL |
109 | } |
110 | } | |
111 | }); | |
112 | ||
32a655c1 SL |
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) | |
32a655c1 SL |
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 { | |
cc61c64b | 131 | tidy_error!(bad, "Found {} features without a gate test.", gate_untested.len()); |
32a655c1 SL |
132 | } |
133 | ||
a7813a04 | 134 | if *bad { |
c30ab7b3 | 135 | return; |
a7813a04 | 136 | } |
7cac9316 XL |
137 | if quiet { |
138 | println!("* {} features", features.len()); | |
139 | return; | |
140 | } | |
a7813a04 XL |
141 | |
142 | let mut lines = Vec::new(); | |
32a655c1 | 143 | for (name, feature) in features.iter() { |
a7813a04 | 144 | lines.push(format!("{:<32} {:<8} {:<12} {:<8}", |
32a655c1 | 145 | name, |
c30ab7b3 SL |
146 | "lang", |
147 | feature.level, | |
148 | feature.since)); | |
a7813a04 XL |
149 | } |
150 | for (name, feature) in lib_features { | |
151 | lines.push(format!("{:<32} {:<8} {:<12} {:<8}", | |
c30ab7b3 SL |
152 | name, |
153 | "lib", | |
154 | feature.level, | |
155 | feature.since)); | |
a7813a04 XL |
156 | } |
157 | ||
158 | lines.sort(); | |
159 | for line in lines { | |
160 | println!("* {}", line); | |
161 | } | |
162 | } | |
163 | ||
164 | fn find_attr_val<'a>(line: &'a str, attr: &str) -> Option<&'a str> { | |
c30ab7b3 SL |
165 | line.find(attr) |
166 | .and_then(|i| line[i..].find('"').map(|j| i + j + 1)) | |
167 | .and_then(|i| line[i..].find('"').map(|j| (i, i + j))) | |
168 | .map(|(i, j)| &line[i..j]) | |
a7813a04 XL |
169 | } |
170 | ||
32a655c1 SL |
171 | fn test_filen_gate(filen_underscore: &str, |
172 | features: &mut HashMap<String, Feature>) -> bool { | |
173 | if filen_underscore.starts_with("feature_gate") { | |
174 | for (n, f) in features.iter_mut() { | |
175 | if filen_underscore == format!("feature_gate_{}", n) { | |
176 | f.has_gate_test = true; | |
177 | return true; | |
178 | } | |
179 | } | |
180 | } | |
181 | return false; | |
182 | } | |
183 | ||
cc61c64b | 184 | pub fn collect_lang_features(base_src_path: &Path) -> HashMap<String, Feature> { |
a7813a04 | 185 | let mut contents = String::new(); |
cc61c64b | 186 | let path = base_src_path.join("libsyntax/feature_gate.rs"); |
a7813a04 XL |
187 | t!(t!(File::open(path)).read_to_string(&mut contents)); |
188 | ||
c30ab7b3 SL |
189 | contents.lines() |
190 | .filter_map(|line| { | |
191 | let mut parts = line.trim().split(","); | |
192 | let level = match parts.next().map(|l| l.trim().trim_left_matches('(')) { | |
193 | Some("active") => Status::Unstable, | |
32a655c1 | 194 | Some("removed") => Status::Removed, |
c30ab7b3 SL |
195 | Some("accepted") => Status::Stable, |
196 | _ => return None, | |
197 | }; | |
198 | let name = parts.next().unwrap().trim(); | |
199 | let since = parts.next().unwrap().trim().trim_matches('"'); | |
32a655c1 SL |
200 | Some((name.to_owned(), |
201 | Feature { | |
202 | level: level, | |
203 | since: since.to_owned(), | |
204 | has_gate_test: false, | |
205 | })) | |
c30ab7b3 SL |
206 | }) |
207 | .collect() | |
a7813a04 | 208 | } |
cc61c64b XL |
209 | |
210 | pub fn collect_lib_features(base_src_path: &Path, | |
211 | bad: &mut bool, | |
212 | features: &HashMap<String, Feature>) -> HashMap<String, Feature> { | |
213 | let mut lib_features = HashMap::<String, Feature>::new(); | |
214 | let mut contents = String::new(); | |
215 | super::walk(base_src_path, | |
216 | &mut |path| super::filter_dirs(path) || path.ends_with("src/test"), | |
217 | &mut |file| { | |
218 | let filename = file.file_name().unwrap().to_string_lossy(); | |
219 | if !filename.ends_with(".rs") || filename == "features.rs" || | |
220 | filename == "diagnostic_list.rs" { | |
221 | return; | |
222 | } | |
223 | ||
224 | contents.truncate(0); | |
225 | t!(t!(File::open(&file), &file).read_to_string(&mut contents)); | |
226 | ||
227 | for (i, line) in contents.lines().enumerate() { | |
228 | let mut err = |msg: &str| { | |
229 | tidy_error!(bad, "{}:{}: {}", file.display(), i + 1, msg); | |
230 | }; | |
231 | let level = if line.contains("[unstable(") { | |
232 | Status::Unstable | |
233 | } else if line.contains("[stable(") { | |
234 | Status::Stable | |
235 | } else { | |
236 | continue; | |
237 | }; | |
238 | let feature_name = match find_attr_val(line, "feature") { | |
239 | Some(name) => name, | |
240 | None => { | |
241 | err("malformed stability attribute"); | |
242 | continue; | |
243 | } | |
244 | }; | |
245 | let since = match find_attr_val(line, "since") { | |
246 | Some(name) => name, | |
247 | None if level == Status::Stable => { | |
248 | err("malformed stability attribute"); | |
249 | continue; | |
250 | } | |
251 | None => "None", | |
252 | }; | |
253 | ||
254 | if features.contains_key(feature_name) { | |
255 | err("duplicating a lang feature"); | |
256 | } | |
257 | if let Some(ref s) = lib_features.get(feature_name) { | |
258 | if s.level != level { | |
259 | err("different stability level than before"); | |
260 | } | |
261 | if s.since != since { | |
262 | err("different `since` than before"); | |
263 | } | |
264 | continue; | |
265 | } | |
266 | lib_features.insert(feature_name.to_owned(), | |
267 | Feature { | |
268 | level: level, | |
269 | since: since.to_owned(), | |
270 | has_gate_test: false, | |
271 | }); | |
272 | } | |
273 | }); | |
274 | lib_features | |
7cac9316 | 275 | } |