Sync all unstable features with Unstable Book; add tidy lint.

Add a tidy lint that checks for...

* Unstable Book sections with no corresponding SUMMARY.md links
* unstable features that don't have Unstable Book sections
* Unstable Book sections that don't have corresponding unstable features
This commit is contained in:
Corey Farwell 2017-03-19 10:45:05 -04:00
parent a9329d3aa3
commit eef2a9598b
10 changed files with 243 additions and 85 deletions

3
src/Cargo.lock generated
View file

@ -926,6 +926,9 @@ dependencies = [
[[package]]
name = "tidy"
version = "0.1.0"
dependencies = [
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "toml"

View file

@ -146,7 +146,6 @@
- [proc_macro](proc-macro.md)
- [proc_macro_internals](proc-macro-internals.md)
- [process_try_wait](process-try-wait.md)
- [pub_restricted](pub-restricted.md)
- [question_mark_carrier](question-mark-carrier.md)
- [quote](quote.md)
- [rand](rand.md)
@ -156,11 +155,11 @@
- [relaxed_adts](relaxed-adts.md)
- [repr_simd](repr-simd.md)
- [retain_hash_collection](retain-hash-collection.md)
- [reverse_cmp_key](reverse-cmp-key.md)
- [rt](rt.md)
- [rustc_attrs](rustc-attrs.md)
- [rustc_diagnostic_macros](rustc-diagnostic-macros.md)
- [rustc_private](rustc-private.md)
- [rustdoc](rustdoc.md)
- [rvalue_static_promotion](rvalue-static-promotion.md)
- [sanitizer_runtime](sanitizer-runtime.md)
- [sanitizer_runtime_lib](sanitizer-runtime-lib.md)
@ -181,6 +180,7 @@
- [step_by](step-by.md)
- [step_trait](step-trait.md)
- [stmt_expr_attributes](stmt-expr-attributes.md)
- [str_checked_slicing](str-checked-slicing.md)
- [str_escape](str-escape.md)
- [str_internals](str-internals.md)
- [struct_field_attributes](struct-field-attributes.md)

View file

@ -1,7 +0,0 @@
# `pub_restricted`
The tracking issue for this feature is: [#32409]
[#38356]: https://github.com/rust-lang/rust/issues/32409
------------------------

View file

@ -0,0 +1,7 @@
# `reverse_cmp_key`
The tracking issue for this feature is: [#40893]
[#40893]: https://github.com/rust-lang/rust/issues/40893
------------------------

View file

@ -1,7 +0,0 @@
# `rustdoc`
The tracking issue for this feature is: [#27812]
[#27812]: https://github.com/rust-lang/rust/issues/27812
------------------------

View file

@ -0,0 +1,7 @@
# `str_checked_slicing`
The tracking issue for this feature is: [#39932]
[#39932]: https://github.com/rust-lang/rust/issues/39932
------------------------

View file

@ -4,3 +4,4 @@ version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
[dependencies]
regex = "0.2"

View file

@ -24,8 +24,8 @@
use std::io::prelude::*;
use std::path::Path;
#[derive(PartialEq)]
enum Status {
#[derive(Debug, PartialEq)]
pub enum Status {
Stable,
Removed,
Unstable,
@ -42,78 +42,21 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
}
}
struct Feature {
level: Status,
since: String,
has_gate_test: bool,
#[derive(Debug)]
pub struct Feature {
pub level: Status,
pub since: String,
pub has_gate_test: bool,
}
pub fn check(path: &Path, bad: &mut bool) {
let mut features = collect_lang_features(&path.join("libsyntax/feature_gate.rs"));
let mut features = collect_lang_features(path);
assert!(!features.is_empty());
let mut lib_features = HashMap::<String, Feature>::new();
let lib_features = collect_lib_features(path, bad, &features);
assert!(!lib_features.is_empty());
let mut contents = String::new();
super::walk(path,
&mut |path| super::filter_dirs(path) || path.ends_with("src/test"),
&mut |file| {
let filename = file.file_name().unwrap().to_string_lossy();
if !filename.ends_with(".rs") || filename == "features.rs" ||
filename == "diagnostic_list.rs" {
return;
}
contents.truncate(0);
t!(t!(File::open(&file), &file).read_to_string(&mut contents));
for (i, line) in contents.lines().enumerate() {
let mut err = |msg: &str| {
println!("{}:{}: {}", file.display(), i + 1, msg);
*bad = true;
};
let level = if line.contains("[unstable(") {
Status::Unstable
} else if line.contains("[stable(") {
Status::Stable
} else {
continue;
};
let feature_name = match find_attr_val(line, "feature") {
Some(name) => name,
None => {
err("malformed stability attribute");
continue;
}
};
let since = match find_attr_val(line, "since") {
Some(name) => name,
None if level == Status::Stable => {
err("malformed stability attribute");
continue;
}
None => "None",
};
if features.contains_key(feature_name) {
err("duplicating a lang feature");
}
if let Some(ref s) = lib_features.get(feature_name) {
if s.level != level {
err("different stability level than before");
}
if s.since != since {
err("different `since` than before");
}
continue;
}
lib_features.insert(feature_name.to_owned(),
Feature {
level: level,
since: since.to_owned(),
has_gate_test: false,
});
}
});
super::walk_many(&[&path.join("test/compile-fail"),
&path.join("test/compile-fail-fulldeps"),
@ -233,8 +176,9 @@ fn test_filen_gate(filen_underscore: &str,
return false;
}
fn collect_lang_features(path: &Path) -> HashMap<String, Feature> {
pub fn collect_lang_features(base_src_path: &Path) -> HashMap<String, Feature> {
let mut contents = String::new();
let path = base_src_path.join("libsyntax/feature_gate.rs");
t!(t!(File::open(path)).read_to_string(&mut contents));
contents.lines()
@ -257,3 +201,71 @@ fn collect_lang_features(path: &Path) -> HashMap<String, Feature> {
})
.collect()
}
pub fn collect_lib_features(base_src_path: &Path,
bad: &mut bool,
features: &HashMap<String, Feature>) -> HashMap<String, Feature> {
let mut lib_features = HashMap::<String, Feature>::new();
let mut contents = String::new();
super::walk(base_src_path,
&mut |path| super::filter_dirs(path) || path.ends_with("src/test"),
&mut |file| {
let filename = file.file_name().unwrap().to_string_lossy();
if !filename.ends_with(".rs") || filename == "features.rs" ||
filename == "diagnostic_list.rs" {
return;
}
contents.truncate(0);
t!(t!(File::open(&file), &file).read_to_string(&mut contents));
for (i, line) in contents.lines().enumerate() {
let mut err = |msg: &str| {
println!("{}:{}: {}", file.display(), i + 1, msg);
*bad = true;
};
let level = if line.contains("[unstable(") {
Status::Unstable
} else if line.contains("[stable(") {
Status::Stable
} else {
continue;
};
let feature_name = match find_attr_val(line, "feature") {
Some(name) => name,
None => {
err("malformed stability attribute");
continue;
}
};
let since = match find_attr_val(line, "since") {
Some(name) => name,
None if level == Status::Stable => {
err("malformed stability attribute");
continue;
}
None => "None",
};
if features.contains_key(feature_name) {
err("duplicating a lang feature");
}
if let Some(ref s) = lib_features.get(feature_name) {
if s.level != level {
err("different stability level than before");
}
if s.since != since {
err("different `since` than before");
}
continue;
}
lib_features.insert(feature_name.to_owned(),
Feature {
level: level,
since: since.to_owned(),
has_gate_test: false,
});
}
});
lib_features
}

View file

@ -14,6 +14,8 @@
//! etc. This is run by default on `make check` and as part of the auto
//! builders.
extern crate regex;
use std::fs;
use std::path::{PathBuf, Path};
use std::env;
@ -37,6 +39,7 @@ macro_rules! t {
mod cargo;
mod pal;
mod deps;
mod unstable_book;
fn main() {
let path = env::args_os().skip(1).next().expect("need an argument");
@ -51,6 +54,7 @@ fn main() {
cargo::check(&path, &mut bad);
features::check(&path, &mut bad);
pal::check(&path, &mut bad);
unstable_book::check(&path, &mut bad);
if !args.iter().any(|s| *s == "--no-vendor") {
deps::check(&path, &mut bad);
}

View file

@ -0,0 +1,138 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::collections::HashSet;
use std::fs;
use std::io::{self, BufRead, Write};
use std::path;
use features::{collect_lang_features, collect_lib_features, Status};
const PATH_STR: &'static str = "doc/unstable-book/src";
const SUMMARY_FILE_NAME: &'static str = "SUMMARY.md";
static EXCLUDE: &'static [&'static str; 2] = &[SUMMARY_FILE_NAME, "the-unstable-book.md"];
/// Build the path to the Unstable Book source directory from the Rust 'src' directory
fn unstable_book_path(base_src_path: &path::Path) -> path::PathBuf {
base_src_path.join(PATH_STR)
}
/// Build the path to the Unstable Book SUMMARY file from the Rust 'src' directory
fn unstable_book_summary_path(base_src_path: &path::Path) -> path::PathBuf {
unstable_book_path(base_src_path).join(SUMMARY_FILE_NAME)
}
/// Open the Unstable Book SUMMARY file
fn open_unstable_book_summary_file(base_src_path: &path::Path) -> fs::File {
fs::File::open(unstable_book_summary_path(base_src_path))
.expect("could not open Unstable Book SUMMARY.md")
}
/// Test to determine if DirEntry is a file
fn dir_entry_is_file(dir_entry: &fs::DirEntry) -> bool {
dir_entry.file_type().expect("could not determine file type of directory entry").is_file()
}
/// Retrieve names of all lang-related unstable features
fn collect_unstable_lang_feature_names(base_src_path: &path::Path) -> HashSet<String> {
collect_lang_features(base_src_path)
.into_iter()
.filter(|&(_, ref f)| f.level == Status::Unstable)
.map(|(ref name, _)| name.to_owned())
.collect()
}
/// Retrieve names of all lib-related unstable features
fn collect_unstable_lib_feature_names(base_src_path: &path::Path) -> HashSet<String> {
let mut bad = true;
let lang_features = collect_lang_features(base_src_path);
collect_lib_features(base_src_path, &mut bad, &lang_features)
.into_iter()
.filter(|&(_, ref f)| f.level == Status::Unstable)
.map(|(ref name, _)| name.to_owned())
.collect()
}
/// Retrieve names of all unstable features
fn collect_unstable_feature_names(base_src_path: &path::Path) -> HashSet<String> {
collect_unstable_lib_feature_names(base_src_path)
.union(&collect_unstable_lang_feature_names(base_src_path))
.map(|n| n.to_owned())
.collect::<HashSet<_, _>>()
}
/// Retrieve file names of all sections in the Unstable Book with:
///
/// * hyphens replaced by underscores
/// * the markdown suffix ('.md') removed
fn collect_unstable_book_section_file_names(base_src_path: &path::Path) -> HashSet<String> {
fs::read_dir(unstable_book_path(base_src_path))
.expect("could not read directory")
.into_iter()
.map(|entry| entry.expect("could not read directory entry"))
.filter(dir_entry_is_file)
.map(|entry| entry.file_name().into_string().unwrap())
.filter(|n| EXCLUDE.iter().all(|e| n != e))
.map(|n| n.trim_right_matches(".md").replace('-', "_"))
.collect()
}
/// Retrieve unstable feature names that are in the Unstable Book SUMMARY file
fn collect_unstable_book_summary_links(base_src_path: &path::Path) -> HashSet<String> {
let summary_link_regex =
::regex::Regex::new(r"^- \[(\S+)\]\(\S+\.md\)").expect("invalid regex");
io::BufReader::new(open_unstable_book_summary_file(base_src_path))
.lines()
.map(|l| l.expect("could not read line from file"))
.filter_map(|line| {
summary_link_regex.captures(&line).map(|c| {
c.get(1)
.unwrap()
.as_str()
.to_owned()
})
})
.collect()
}
pub fn check(path: &path::Path, bad: &mut bool) {
let unstable_feature_names = collect_unstable_feature_names(path);
let unstable_book_section_file_names = collect_unstable_book_section_file_names(path);
let unstable_book_links = collect_unstable_book_summary_links(path);
// Check for Unstable Book section names with no corresponding SUMMARY.md link
for feature_name in &unstable_book_section_file_names - &unstable_book_links {
*bad = true;
writeln!(io::stderr(),
"The Unstable Book section '{}' needs to have a link in SUMMARY.md",
feature_name)
.expect("could not write to stderr")
}
// Check for unstable features that don't have Unstable Book sections
for feature_name in &unstable_feature_names - &unstable_book_section_file_names {
*bad = true;
writeln!(io::stderr(),
"Unstable feature '{}' needs to have a section in The Unstable Book",
feature_name)
.expect("could not write to stderr")
}
// Check for Unstable Book sections that don't have a corresponding unstable feature
for feature_name in &unstable_book_section_file_names - &unstable_feature_names {
*bad = true;
writeln!(io::stderr(),
"The Unstable Book has a section '{}' which doesn't correspond \
to an unstable feature",
feature_name)
.expect("could not write to stderr")
}
}