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.
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 ensure that unstable features are all in order
13 //! This check will ensure properties like:
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
21 use std::collections::HashMap;
24 use std::io::prelude::*;
27 #[derive(Debug, PartialEq)]
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",
41 fmt::Display::fmt(as_str, f)
49 pub has_gate_test: bool,
52 pub fn check(path: &Path, bad: &mut bool) {
53 let mut features = collect_lang_features(path);
54 assert!(!features.is_empty());
56 let lib_features = collect_lib_features(path, bad, &features);
57 assert!(!lib_features.is_empty());
59 let mut contents = String::new();
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),
66 let filename = file.file_name().unwrap().to_string_lossy();
67 if !filename.ends_with(".rs") || filename == "features.rs" ||
68 filename == "diagnostic_list.rs" {
72 let filen_underscore = filename.replace("-","_").replace(".rs","");
73 test_filen_gate(&filen_underscore, &mut features);
76 t!(t!(File::open(&file), &file).read_to_string(&mut contents));
78 for (i, line) in contents.lines().enumerate() {
79 let mut err = |msg: &str| {
80 println!("{}:{}: {}", file.display(), i + 1, msg);
84 let gate_test_str = "gate-test-";
86 if !line.contains(gate_test_str) {
90 let feature_name = match line.find(gate_test_str) {
92 &line[i+gate_test_str.len()..line[i+1..].find(' ').unwrap_or(line.len())]
96 let found_feature = features.get_mut(feature_name)
97 .map(|v| { v.has_gate_test = true; () })
100 let found_lib_feature = features.get_mut(feature_name)
101 .map(|v| { v.has_gate_test = true; () })
104 if !(found_feature || found_lib_feature) {
105 err(&format!("gate-test test found referencing a nonexistent feature '{}'",
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<_>>();
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.",
128 if gate_untested.len() > 0 {
129 println!("Found {} features without a gate test.", gate_untested.len());
137 let mut lines = Vec::new();
138 for (name, feature) in features.iter() {
139 lines.push(format!("{:<32} {:<8} {:<12} {:<8}",
145 for (name, feature) in lib_features {
146 lines.push(format!("{:<32} {:<8} {:<12} {:<8}",
155 println!("* {}", line);
159 fn find_attr_val<'a>(line: &'a str, attr: &str) -> Option<&'a str> {
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])
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;
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));
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,
193 let name = parts.next().unwrap().trim();
194 let since = parts.next().unwrap().trim().trim_matches('"');
195 Some((name.to_owned(),
198 since: since.to_owned(),
199 has_gate_test: false,
205 pub fn collect_lib_features(base_src_path: &Path,
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"),
213 let filename = file.file_name().unwrap().to_string_lossy();
214 if !filename.ends_with(".rs") || filename == "features.rs" ||
215 filename == "diagnostic_list.rs" {
219 contents.truncate(0);
220 t!(t!(File::open(&file), &file).read_to_string(&mut contents));
222 for (i, line) in contents.lines().enumerate() {
223 let mut err = |msg: &str| {
224 println!("{}:{}: {}", file.display(), i + 1, msg);
227 let level = if line.contains("[unstable(") {
229 } else if line.contains("[stable(") {
234 let feature_name = match find_attr_val(line, "feature") {
237 err("malformed stability attribute");
241 let since = match find_attr_val(line, "since") {
243 None if level == Status::Stable => {
244 err("malformed stability attribute");
250 if features.contains_key(feature_name) {
251 err("duplicating a lang feature");
253 if let Some(ref s) = lib_features.get(feature_name) {
254 if s.level != level {
255 err("different stability level than before");
257 if s.since != since {
258 err("different `since` than before");
262 lib_features.insert(feature_name.to_owned(),
265 since: since.to_owned(),
266 has_gate_test: false,