tidy/
unstable_book.rs

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