tidy/
unstable_book.rs

1use std::collections::BTreeSet;
2use std::fs;
3use std::path::{Path, PathBuf};
4
5use crate::features::{CollectedFeatures, Features, Status};
6
7pub const PATH_STR: &str = "doc/unstable-book";
8
9pub const COMPILER_FLAGS_DIR: &str = "src/compiler-flags";
10
11pub const LANG_FEATURES_DIR: &str = "src/language-features";
12
13pub const LIB_FEATURES_DIR: &str = "src/library-features";
14
15/// Builds the path to the Unstable Book source directory from the Rust 'src' directory.
16pub fn unstable_book_path(base_src_path: &Path) -> PathBuf {
17    base_src_path.join(PATH_STR)
18}
19
20/// Builds the path to the directory where the features are documented within the Unstable Book
21/// source directory.
22pub fn unstable_book_lang_features_path(base_src_path: &Path) -> PathBuf {
23    unstable_book_path(base_src_path).join(LANG_FEATURES_DIR)
24}
25
26/// Builds the path to the directory where the features are documented within the Unstable Book
27/// source directory.
28pub fn unstable_book_lib_features_path(base_src_path: &Path) -> PathBuf {
29    unstable_book_path(base_src_path).join(LIB_FEATURES_DIR)
30}
31
32/// Tests whether `DirEntry` is a file.
33fn dir_entry_is_file(dir_entry: &fs::DirEntry) -> bool {
34    dir_entry.file_type().expect("could not determine file type of directory entry").is_file()
35}
36
37/// Retrieves names of all unstable features.
38pub fn collect_unstable_feature_names(features: &Features) -> BTreeSet<String> {
39    features
40        .iter()
41        .filter(|&(_, ref f)| f.level == Status::Unstable)
42        .map(|(name, _)| name.replace('_', "-"))
43        .collect()
44}
45
46pub fn collect_unstable_book_section_file_names(dir: &Path) -> BTreeSet<String> {
47    fs::read_dir(dir)
48        .expect("could not read directory")
49        .map(|entry| entry.expect("could not read directory entry"))
50        .filter(dir_entry_is_file)
51        .map(|entry| entry.path())
52        .filter(|path| path.extension().map(|e| e.to_str().unwrap()) == Some("md"))
53        .map(|path| path.file_stem().unwrap().to_str().unwrap().into())
54        .collect()
55}
56
57/// Retrieves file names of all library feature sections in the Unstable Book with:
58///
59/// * hyphens replaced by underscores,
60/// * the markdown suffix ('.md') removed.
61fn collect_unstable_book_lang_features_section_file_names(
62    base_src_path: &Path,
63) -> BTreeSet<String> {
64    collect_unstable_book_section_file_names(&unstable_book_lang_features_path(base_src_path))
65}
66
67/// Retrieves file names of all language feature sections in the Unstable Book with:
68///
69/// * hyphens replaced by underscores,
70/// * the markdown suffix ('.md') removed.
71fn collect_unstable_book_lib_features_section_file_names(base_src_path: &Path) -> BTreeSet<String> {
72    collect_unstable_book_section_file_names(&unstable_book_lib_features_path(base_src_path))
73}
74
75/// Would switching underscores for dashes work?
76fn maybe_suggest_dashes(names: &BTreeSet<String>, feature_name: &str, bad: &mut bool) {
77    let with_dashes = feature_name.replace('_', "-");
78    if names.contains(&with_dashes) {
79        tidy_error!(
80            bad,
81            "the file `{}.md` contains underscores; use dashes instead: `{}.md`",
82            feature_name,
83            with_dashes,
84        );
85    }
86}
87
88pub fn check(path: &Path, features: CollectedFeatures, bad: &mut bool) {
89    let lang_features = features.lang;
90    let lib_features = features
91        .lib
92        .into_iter()
93        .filter(|&(ref name, _)| !lang_features.contains_key(name))
94        .collect::<Features>();
95
96    // Library features
97    let unstable_lib_feature_names = collect_unstable_feature_names(&lib_features);
98    let unstable_book_lib_features_section_file_names =
99        collect_unstable_book_lib_features_section_file_names(path);
100
101    // Language features
102    let unstable_lang_feature_names = collect_unstable_feature_names(&lang_features);
103    let unstable_book_lang_features_section_file_names =
104        collect_unstable_book_lang_features_section_file_names(path);
105
106    // Check for Unstable Book sections that don't have a corresponding unstable feature
107    for feature_name in &unstable_book_lib_features_section_file_names - &unstable_lib_feature_names
108    {
109        tidy_error!(
110            bad,
111            "The Unstable Book has a 'library feature' section '{}' which doesn't \
112                         correspond to an unstable library feature",
113            feature_name
114        );
115        maybe_suggest_dashes(&unstable_lib_feature_names, &feature_name, bad);
116    }
117
118    // Check for Unstable Book sections that don't have a corresponding unstable feature.
119    for feature_name in
120        &unstable_book_lang_features_section_file_names - &unstable_lang_feature_names
121    {
122        tidy_error!(
123            bad,
124            "The Unstable Book has a 'language feature' section '{}' which doesn't \
125                     correspond to an unstable language feature",
126            feature_name
127        );
128        maybe_suggest_dashes(&unstable_lang_feature_names, &feature_name, bad);
129    }
130
131    // List unstable features that don't have Unstable Book sections.
132    // Remove the comment marker if you want the list printed.
133    /*
134    println!("Lib features without unstable book sections:");
135    for feature_name in &unstable_lang_feature_names -
136                        &unstable_book_lang_features_section_file_names {
137        println!("    * {} {:?}", feature_name, lib_features[&feature_name].tracking_issue);
138    }
139
140    println!("Lang features without unstable book sections:");
141    for feature_name in &unstable_lib_feature_names-
142                        &unstable_book_lib_features_section_file_names {
143        println!("    * {} {:?}", feature_name, lang_features[&feature_name].tracking_issue);
144    }
145    // */
146}