aboutsummaryrefslogtreecommitdiff
path: root/xtask/tests/tidy.rs
diff options
context:
space:
mode:
Diffstat (limited to 'xtask/tests/tidy.rs')
-rw-r--r--xtask/tests/tidy.rs182
1 files changed, 182 insertions, 0 deletions
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs
new file mode 100644
index 000000000..2e9fcf07c
--- /dev/null
+++ b/xtask/tests/tidy.rs
@@ -0,0 +1,182 @@
1use std::{
2 collections::HashMap,
3 path::{Path, PathBuf},
4};
5
6use xtask::{
7 codegen::{self, Mode},
8 not_bash::fs2,
9 project_root, run_rustfmt, rust_files,
10};
11
12#[test]
13fn generated_grammar_is_fresh() {
14 if let Err(error) = codegen::generate_syntax(Mode::Verify) {
15 panic!("{}. Please update it by running `cargo xtask codegen`", error);
16 }
17}
18
19#[test]
20fn generated_tests_are_fresh() {
21 if let Err(error) = codegen::generate_parser_tests(Mode::Verify) {
22 panic!("{}. Please update tests by running `cargo xtask codegen`", error);
23 }
24}
25
26#[test]
27fn generated_assists_are_fresh() {
28 if let Err(error) = codegen::generate_assists_docs(Mode::Verify) {
29 panic!("{}. Please update assists by running `cargo xtask codegen`", error);
30 }
31}
32
33#[test]
34fn check_code_formatting() {
35 if let Err(error) = run_rustfmt(Mode::Verify) {
36 panic!("{}. Please format the code by running `cargo format`", error);
37 }
38}
39
40#[test]
41fn rust_files_are_tidy() {
42 let mut tidy_docs = TidyDocs::default();
43 for path in rust_files(&project_root().join("crates")) {
44 let text = fs2::read_to_string(&path).unwrap();
45 check_todo(&path, &text);
46 check_trailing_ws(&path, &text);
47 tidy_docs.visit(&path, &text);
48 }
49 tidy_docs.finish();
50}
51
52fn check_todo(path: &Path, text: &str) {
53 let whitelist = &[
54 // This file itself is whitelisted since this test itself contains matches.
55 "tests/cli.rs",
56 // Some of our assists generate `todo!()` so those files are whitelisted.
57 "tests/generated.rs",
58 "handlers/add_missing_impl_members.rs",
59 "handlers/add_function.rs",
60 "handlers/add_turbo_fish.rs",
61 // To support generating `todo!()` in assists, we have `expr_todo()` in ast::make.
62 "ast/make.rs",
63 ];
64 if whitelist.iter().any(|p| path.ends_with(p)) {
65 return;
66 }
67 if text.contains("TODO") || text.contains("TOOD") || text.contains("todo!") {
68 panic!(
69 "\nTODO markers or todo! macros should not be committed to the master branch,\n\
70 use FIXME instead\n\
71 {}\n",
72 path.display(),
73 )
74 }
75}
76
77fn check_trailing_ws(path: &Path, text: &str) {
78 if is_exclude_dir(path, &["test_data"]) {
79 return;
80 }
81 for (line_number, line) in text.lines().enumerate() {
82 if line.chars().last().map(char::is_whitespace) == Some(true) {
83 panic!("Trailing whitespace in {} at line {}", path.display(), line_number)
84 }
85 }
86}
87
88#[derive(Default)]
89struct TidyDocs {
90 missing_docs: Vec<String>,
91 contains_fixme: Vec<PathBuf>,
92}
93
94impl TidyDocs {
95 fn visit(&mut self, path: &Path, text: &str) {
96 // Test hopefully don't really need comments, and for assists we already
97 // have special comments which are source of doc tests and user docs.
98 if is_exclude_dir(path, &["tests", "test_data", "handlers"]) {
99 return;
100 }
101
102 if is_exclude_file(path) {
103 return;
104 }
105
106 let first_line = match text.lines().next() {
107 Some(it) => it,
108 None => return,
109 };
110
111 if first_line.starts_with("//!") {
112 if first_line.contains("FIXME") {
113 self.contains_fixme.push(path.to_path_buf())
114 }
115 } else {
116 self.missing_docs.push(path.display().to_string());
117 }
118
119 fn is_exclude_file(d: &Path) -> bool {
120 let file_names = ["tests.rs"];
121
122 d.file_name()
123 .unwrap_or_default()
124 .to_str()
125 .map(|f_n| file_names.iter().any(|name| *name == f_n))
126 .unwrap_or(false)
127 }
128 }
129
130 fn finish(self) {
131 if !self.missing_docs.is_empty() {
132 panic!(
133 "\nMissing docs strings\n\n\
134 modules:\n{}\n\n",
135 self.missing_docs.join("\n")
136 )
137 }
138
139 let whitelist = [
140 "ra_hir",
141 "ra_hir_expand",
142 "ra_ide",
143 "ra_mbe",
144 "ra_parser",
145 "ra_prof",
146 "ra_project_model",
147 "ra_syntax",
148 "ra_tt",
149 "ra_hir_ty",
150 ];
151
152 let mut has_fixmes =
153 whitelist.iter().map(|it| (*it, false)).collect::<HashMap<&str, bool>>();
154 'outer: for path in self.contains_fixme {
155 for krate in whitelist.iter() {
156 if path.components().any(|it| it.as_os_str() == *krate) {
157 has_fixmes.insert(krate, true);
158 continue 'outer;
159 }
160 }
161 panic!("FIXME doc in a fully-documented crate: {}", path.display())
162 }
163
164 for (krate, has_fixme) in has_fixmes.iter() {
165 if !has_fixme {
166 panic!("crate {} is fully documented, remove it from the white list", krate)
167 }
168 }
169 }
170}
171
172fn is_exclude_dir(p: &Path, dirs_to_exclude: &[&str]) -> bool {
173 let mut cur_path = p;
174 while let Some(path) = cur_path.parent() {
175 if dirs_to_exclude.iter().any(|dir| path.ends_with(dir)) {
176 return true;
177 }
178 cur_path = path;
179 }
180
181 false
182}