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