aboutsummaryrefslogtreecommitdiff
path: root/xtask
diff options
context:
space:
mode:
Diffstat (limited to 'xtask')
-rw-r--r--xtask/src/codegen.rs27
-rw-r--r--xtask/src/codegen/gen_assists_docs.rs10
-rw-r--r--xtask/src/codegen/gen_feature_docs.rs88
-rw-r--r--xtask/src/lib.rs6
-rw-r--r--xtask/src/main.rs1
-rw-r--r--xtask/tests/tidy.rs14
6 files changed, 131 insertions, 15 deletions
diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs
index b4907f4b2..f47d54125 100644
--- a/xtask/src/codegen.rs
+++ b/xtask/src/codegen.rs
@@ -8,14 +8,15 @@
8mod gen_syntax; 8mod gen_syntax;
9mod gen_parser_tests; 9mod gen_parser_tests;
10mod gen_assists_docs; 10mod gen_assists_docs;
11mod gen_feature_docs;
11 12
12use std::{mem, path::Path}; 13use std::{mem, path::Path};
13 14
14use crate::{not_bash::fs2, Result}; 15use crate::{not_bash::fs2, Result};
15 16
16pub use self::{ 17pub use self::{
17 gen_assists_docs::generate_assists_docs, gen_parser_tests::generate_parser_tests, 18 gen_assists_docs::generate_assists_docs, gen_feature_docs::generate_feature_docs,
18 gen_syntax::generate_syntax, 19 gen_parser_tests::generate_parser_tests, gen_syntax::generate_syntax,
19}; 20};
20 21
21const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar"; 22const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar";
@@ -40,7 +41,7 @@ pub enum Mode {
40/// With verify = false, 41/// With verify = false,
41fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> { 42fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> {
42 match fs2::read_to_string(path) { 43 match fs2::read_to_string(path) {
43 Ok(ref old_contents) if normalize(old_contents) == normalize(contents) => { 44 Ok(old_contents) if normalize(&old_contents) == normalize(contents) => {
44 return Ok(()); 45 return Ok(());
45 } 46 }
46 _ => (), 47 _ => (),
@@ -61,8 +62,24 @@ fn extract_comment_blocks(text: &str) -> Vec<Vec<String>> {
61 do_extract_comment_blocks(text, false) 62 do_extract_comment_blocks(text, false)
62} 63}
63 64
64fn extract_comment_blocks_with_empty_lines(text: &str) -> Vec<Vec<String>> { 65fn extract_comment_blocks_with_empty_lines(tag: &str, text: &str) -> Vec<CommentBlock> {
65 do_extract_comment_blocks(text, true) 66 assert!(tag.starts_with(char::is_uppercase));
67 let tag = format!("{}:", tag);
68 let mut res = Vec::new();
69 for mut block in do_extract_comment_blocks(text, true) {
70 let first = block.remove(0);
71 if first.starts_with(&tag) {
72 let id = first[tag.len()..].trim().to_string();
73 let block = CommentBlock { id, contents: block };
74 res.push(block);
75 }
76 }
77 res
78}
79
80struct CommentBlock {
81 id: String,
82 contents: Vec<String>,
66} 83}
67 84
68fn do_extract_comment_blocks(text: &str, allow_blocks_with_empty_lines: bool) -> Vec<Vec<String>> { 85fn do_extract_comment_blocks(text: &str, allow_blocks_with_empty_lines: bool) -> Vec<Vec<String>> {
diff --git a/xtask/src/codegen/gen_assists_docs.rs b/xtask/src/codegen/gen_assists_docs.rs
index 20dcde820..6ebeb8aea 100644
--- a/xtask/src/codegen/gen_assists_docs.rs
+++ b/xtask/src/codegen/gen_assists_docs.rs
@@ -33,22 +33,18 @@ impl Assist {
33 33
34 fn collect_file(acc: &mut Vec<Assist>, path: &Path) -> Result<()> { 34 fn collect_file(acc: &mut Vec<Assist>, path: &Path) -> Result<()> {
35 let text = fs::read_to_string(path)?; 35 let text = fs::read_to_string(path)?;
36 let comment_blocks = extract_comment_blocks_with_empty_lines(&text); 36 let comment_blocks = extract_comment_blocks_with_empty_lines("Assist", &text);
37 37
38 for block in comment_blocks { 38 for block in comment_blocks {
39 // FIXME: doesn't support blank lines yet, need to tweak 39 // FIXME: doesn't support blank lines yet, need to tweak
40 // `extract_comment_blocks` for that. 40 // `extract_comment_blocks` for that.
41 let mut lines = block.iter(); 41 let id = block.id;
42 let first_line = lines.next().unwrap();
43 if !first_line.starts_with("Assist: ") {
44 continue;
45 }
46 let id = first_line["Assist: ".len()..].to_string();
47 assert!( 42 assert!(
48 id.chars().all(|it| it.is_ascii_lowercase() || it == '_'), 43 id.chars().all(|it| it.is_ascii_lowercase() || it == '_'),
49 "invalid assist id: {:?}", 44 "invalid assist id: {:?}",
50 id 45 id
51 ); 46 );
47 let mut lines = block.contents.iter();
52 48
53 let doc = take_until(lines.by_ref(), "```").trim().to_string(); 49 let doc = take_until(lines.by_ref(), "```").trim().to_string();
54 assert!( 50 assert!(
diff --git a/xtask/src/codegen/gen_feature_docs.rs b/xtask/src/codegen/gen_feature_docs.rs
new file mode 100644
index 000000000..dbe583e8e
--- /dev/null
+++ b/xtask/src/codegen/gen_feature_docs.rs
@@ -0,0 +1,88 @@
1//! Generates `assists.md` documentation.
2
3use std::{fmt, fs, path::PathBuf};
4
5use crate::{
6 codegen::{self, extract_comment_blocks_with_empty_lines, Mode},
7 project_root, rust_files, Result,
8};
9
10pub fn generate_feature_docs(mode: Mode) -> Result<()> {
11 let features = Feature::collect()?;
12 let contents = features.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n");
13 let contents = contents.trim().to_string() + "\n";
14 let dst = project_root().join("docs/user/generated_features.adoc");
15 codegen::update(&dst, &contents, mode)?;
16 Ok(())
17}
18
19#[derive(Debug)]
20struct Feature {
21 id: String,
22 path: PathBuf,
23 doc: String,
24}
25
26impl Feature {
27 fn collect() -> Result<Vec<Feature>> {
28 let mut res = Vec::new();
29 for path in rust_files(&project_root()) {
30 collect_file(&mut res, path)?;
31 }
32 res.sort_by(|lhs, rhs| lhs.id.cmp(&rhs.id));
33 return Ok(res);
34
35 fn collect_file(acc: &mut Vec<Feature>, path: PathBuf) -> Result<()> {
36 let text = fs::read_to_string(&path)?;
37 let comment_blocks = extract_comment_blocks_with_empty_lines("Feature", &text);
38
39 for block in comment_blocks {
40 let id = block.id;
41 assert!(is_valid_feature_name(&id), "invalid feature name: {:?}", id);
42 let doc = block.contents.join("\n");
43 acc.push(Feature { id, path: path.clone(), doc })
44 }
45
46 Ok(())
47 }
48 }
49}
50
51fn is_valid_feature_name(feature: &str) -> bool {
52 'word: for word in feature.split_whitespace() {
53 for &short in ["to", "and"].iter() {
54 if word == short {
55 continue 'word;
56 }
57 }
58 for &short in ["To", "And"].iter() {
59 if word == short {
60 return false;
61 }
62 }
63 if !word.starts_with(char::is_uppercase) {
64 return false;
65 }
66 }
67 true
68}
69
70impl fmt::Display for Feature {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 writeln!(f, "=== {}", self.id)?;
73 let path = self.path.strip_prefix(&project_root()).unwrap().display().to_string();
74 let path = path.replace('\\', "/");
75 let name = self.path.file_name().unwrap();
76
77 //FIXME: generate line number as well
78 writeln!(
79 f,
80 "**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/{}[{}]",
81 path,
82 name.to_str().unwrap(),
83 )?;
84
85 writeln!(f, "{}", self.doc)?;
86 Ok(())
87 }
88}
diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs
index 2b7a461e5..06043d19f 100644
--- a/xtask/src/lib.rs
+++ b/xtask/src/lib.rs
@@ -191,7 +191,11 @@ Release: release:{}[]
191 let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n)); 191 let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n));
192 fs2::write(&path, &contents)?; 192 fs2::write(&path, &contents)?;
193 193
194 fs2::copy(project_root().join("./docs/user/readme.adoc"), website_root.join("manual.adoc"))?; 194 for &adoc in ["manual.adoc", "generated_features.adoc"].iter() {
195 let src = project_root().join("./docs/user/").join(adoc);
196 let dst = website_root.join(adoc);
197 fs2::copy(src, dst)?;
198 }
195 199
196 let tags = run!("git tag --list"; echo = false)?; 200 let tags = run!("git tag --list"; echo = false)?;
197 let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap(); 201 let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap();
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index dff3ce4a1..9d7cdd114 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -75,6 +75,7 @@ FLAGS:
75 codegen::generate_syntax(Mode::Overwrite)?; 75 codegen::generate_syntax(Mode::Overwrite)?;
76 codegen::generate_parser_tests(Mode::Overwrite)?; 76 codegen::generate_parser_tests(Mode::Overwrite)?;
77 codegen::generate_assists_docs(Mode::Overwrite)?; 77 codegen::generate_assists_docs(Mode::Overwrite)?;
78 codegen::generate_feature_docs(Mode::Overwrite)?;
78 Ok(()) 79 Ok(())
79 } 80 }
80 "format" => { 81 "format" => {
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs
index 2e9fcf07c..4ac5d929f 100644
--- a/xtask/tests/tidy.rs
+++ b/xtask/tests/tidy.rs
@@ -31,6 +31,13 @@ fn generated_assists_are_fresh() {
31} 31}
32 32
33#[test] 33#[test]
34fn generated_features_are_fresh() {
35 if let Err(error) = codegen::generate_feature_docs(Mode::Verify) {
36 panic!("{}. Please update features by running `cargo xtask codegen`", error);
37 }
38}
39
40#[test]
34fn check_code_formatting() { 41fn check_code_formatting() {
35 if let Err(error) = run_rustfmt(Mode::Verify) { 42 if let Err(error) = run_rustfmt(Mode::Verify) {
36 panic!("{}. Please format the code by running `cargo format`", error); 43 panic!("{}. Please format the code by running `cargo format`", error);
@@ -95,7 +102,7 @@ impl TidyDocs {
95 fn visit(&mut self, path: &Path, text: &str) { 102 fn visit(&mut self, path: &Path, text: &str) {
96 // Test hopefully don't really need comments, and for assists we already 103 // 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. 104 // have special comments which are source of doc tests and user docs.
98 if is_exclude_dir(path, &["tests", "test_data", "handlers"]) { 105 if is_exclude_dir(path, &["tests", "test_data"]) {
99 return; 106 return;
100 } 107 }
101 108
@@ -110,9 +117,12 @@ impl TidyDocs {
110 117
111 if first_line.starts_with("//!") { 118 if first_line.starts_with("//!") {
112 if first_line.contains("FIXME") { 119 if first_line.contains("FIXME") {
113 self.contains_fixme.push(path.to_path_buf()) 120 self.contains_fixme.push(path.to_path_buf());
114 } 121 }
115 } else { 122 } else {
123 if text.contains("// Feature:") || text.contains("// Assist:") {
124 return;
125 }
116 self.missing_docs.push(path.display().to_string()); 126 self.missing_docs.push(path.display().to_string());
117 } 127 }
118 128