]> git.proxmox.com Git - rustc.git/blame - src/tools/rust-analyzer/crates/ide-assists/src/tests/sourcegen.rs
New upstream version 1.68.2+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / ide-assists / src / tests / sourcegen.rs
CommitLineData
064997fb
FG
1//! Generates `assists.md` documentation.
2
3use std::{fmt, fs, path::Path};
4
5use test_utils::project_root;
6
7#[test]
8fn sourcegen_assists_docs() {
9 let assists = Assist::collect();
10
11 {
12 // Generate doctests.
13
14 let mut buf = "
15use super::check_doc_test;
16"
17 .to_string();
18 for assist in assists.iter() {
19 for (idx, section) in assist.sections.iter().enumerate() {
20 let test_id =
f25598a0 21 if idx == 0 { assist.id.clone() } else { format!("{}_{idx}", &assist.id) };
064997fb
FG
22 let test = format!(
23 r######"
24#[test]
25fn doctest_{}() {{
26 check_doc_test(
27 "{}",
28r#####"
29{}"#####, r#####"
30{}"#####)
31}}
32"######,
33 &test_id,
34 &assist.id,
35 reveal_hash_comments(&section.before),
36 reveal_hash_comments(&section.after)
37 );
38
39 buf.push_str(&test)
40 }
41 }
42 let buf = sourcegen::add_preamble("sourcegen_assists_docs", sourcegen::reformat(buf));
43 sourcegen::ensure_file_contents(
44 &project_root().join("crates/ide-assists/src/tests/generated.rs"),
45 &buf,
46 );
47 }
48
49 {
50 // Generate assists manual. Note that we do _not_ commit manual to the
51 // git repo. Instead, `cargo xtask release` runs this test before making
52 // a release.
53
54 let contents = sourcegen::add_preamble(
55 "sourcegen_assists_docs",
56 assists.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n"),
57 );
58 let dst = project_root().join("docs/user/generated_assists.adoc");
59 fs::write(dst, contents).unwrap();
60 }
61}
62
63#[derive(Debug)]
64struct Section {
65 doc: String,
66 before: String,
67 after: String,
68}
69
70#[derive(Debug)]
71struct Assist {
72 id: String,
73 location: sourcegen::Location,
74 sections: Vec<Section>,
75}
76
77impl Assist {
78 fn collect() -> Vec<Assist> {
79 let handlers_dir = project_root().join("crates/ide-assists/src/handlers");
80
81 let mut res = Vec::new();
82 for path in sourcegen::list_rust_files(&handlers_dir) {
83 collect_file(&mut res, path.as_path());
84 }
85 res.sort_by(|lhs, rhs| lhs.id.cmp(&rhs.id));
86 return res;
87
88 fn collect_file(acc: &mut Vec<Assist>, path: &Path) {
89 let text = fs::read_to_string(path).unwrap();
90 let comment_blocks = sourcegen::CommentBlock::extract("Assist", &text);
91
92 for block in comment_blocks {
93 // FIXME: doesn't support blank lines yet, need to tweak
94 // `extract_comment_blocks` for that.
95 let id = block.id;
96 assert!(
97 id.chars().all(|it| it.is_ascii_lowercase() || it == '_'),
f25598a0 98 "invalid assist id: {id:?}"
064997fb
FG
99 );
100 let mut lines = block.contents.iter().peekable();
101 let location = sourcegen::Location { file: path.to_path_buf(), line: block.line };
102 let mut assist = Assist { id, location, sections: Vec::new() };
103
104 while lines.peek().is_some() {
105 let doc = take_until(lines.by_ref(), "```").trim().to_string();
106 assert!(
107 (doc.chars().next().unwrap().is_ascii_uppercase() && doc.ends_with('.'))
108 || assist.sections.len() > 0,
109 "\n\n{}: assist docs should be proper sentences, with capitalization and a full stop at the end.\n\n{}\n\n",
110 &assist.id,
111 doc,
112 );
113
114 let before = take_until(lines.by_ref(), "```");
115
116 assert_eq!(lines.next().unwrap().as_str(), "->");
117 assert_eq!(lines.next().unwrap().as_str(), "```");
118 let after = take_until(lines.by_ref(), "```");
119
120 assist.sections.push(Section { doc, before, after });
121 }
122
123 acc.push(assist)
124 }
125 }
126
127 fn take_until<'a>(lines: impl Iterator<Item = &'a String>, marker: &str) -> String {
128 let mut buf = Vec::new();
129 for line in lines {
130 if line == marker {
131 break;
132 }
133 buf.push(line.clone());
134 }
135 buf.join("\n")
136 }
137 }
138}
139
140impl fmt::Display for Assist {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 let _ = writeln!(
143 f,
144 "[discrete]\n=== `{}`
145**Source:** {}",
146 self.id, self.location,
147 );
148
149 for section in &self.sections {
150 let before = section.before.replace("$0", "┃"); // Unicode pseudo-graphics bar
151 let after = section.after.replace("$0", "┃");
152 let _ = writeln!(
153 f,
154 "
155{}
156
157.Before
158```rust
159{}```
160
161.After
162```rust
163{}```",
164 section.doc,
165 hide_hash_comments(&before),
166 hide_hash_comments(&after)
167 );
168 }
169
170 Ok(())
171 }
172}
173
174fn hide_hash_comments(text: &str) -> String {
175 text.split('\n') // want final newline
176 .filter(|&it| !(it.starts_with("# ") || it == "#"))
f25598a0 177 .map(|it| format!("{it}\n"))
064997fb
FG
178 .collect()
179}
180
181fn reveal_hash_comments(text: &str) -> String {
182 text.split('\n') // want final newline
183 .map(|it| {
184 if let Some(stripped) = it.strip_prefix("# ") {
185 stripped
186 } else if it == "#" {
187 ""
188 } else {
189 it
190 }
191 })
f25598a0 192 .map(|it| format!("{it}\n"))
064997fb
FG
193 .collect()
194}