aboutsummaryrefslogtreecommitdiff
path: root/xtask
diff options
context:
space:
mode:
Diffstat (limited to 'xtask')
-rw-r--r--xtask/src/codegen.rs9
-rw-r--r--xtask/src/codegen/gen_assists_docs.rs4
-rw-r--r--xtask/src/codegen/gen_diagnostic_docs.rs74
-rw-r--r--xtask/src/codegen/gen_feature_docs.rs4
-rw-r--r--xtask/src/codegen/gen_lint_completions.rs113
-rw-r--r--xtask/src/codegen/gen_parser_tests.rs2
-rw-r--r--xtask/src/install.rs49
-rw-r--r--xtask/src/main.rs19
-rw-r--r--xtask/src/release.rs9
-rw-r--r--xtask/tests/tidy.rs11
10 files changed, 267 insertions, 27 deletions
diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs
index 3ee4c1adf..adea053b6 100644
--- a/xtask/src/codegen.rs
+++ b/xtask/src/codegen.rs
@@ -9,7 +9,8 @@ mod gen_syntax;
9mod gen_parser_tests; 9mod gen_parser_tests;
10mod gen_assists_docs; 10mod gen_assists_docs;
11mod gen_feature_docs; 11mod gen_feature_docs;
12mod gen_features; 12mod gen_lint_completions;
13mod gen_diagnostic_docs;
13 14
14use std::{ 15use std::{
15 fmt, mem, 16 fmt, mem,
@@ -21,8 +22,9 @@ use crate::{ensure_rustfmt, project_root, Result};
21 22
22pub use self::{ 23pub use self::{
23 gen_assists_docs::{generate_assists_docs, generate_assists_tests}, 24 gen_assists_docs::{generate_assists_docs, generate_assists_tests},
25 gen_diagnostic_docs::generate_diagnostic_docs,
24 gen_feature_docs::generate_feature_docs, 26 gen_feature_docs::generate_feature_docs,
25 gen_features::generate_features, 27 gen_lint_completions::generate_lint_completions,
26 gen_parser_tests::generate_parser_tests, 28 gen_parser_tests::generate_parser_tests,
27 gen_syntax::generate_syntax, 29 gen_syntax::generate_syntax,
28}; 30};
@@ -40,13 +42,14 @@ pub struct CodegenCmd {
40impl CodegenCmd { 42impl CodegenCmd {
41 pub fn run(self) -> Result<()> { 43 pub fn run(self) -> Result<()> {
42 if self.features { 44 if self.features {
43 generate_features(Mode::Overwrite)?; 45 generate_lint_completions(Mode::Overwrite)?;
44 } 46 }
45 generate_syntax(Mode::Overwrite)?; 47 generate_syntax(Mode::Overwrite)?;
46 generate_parser_tests(Mode::Overwrite)?; 48 generate_parser_tests(Mode::Overwrite)?;
47 generate_assists_tests(Mode::Overwrite)?; 49 generate_assists_tests(Mode::Overwrite)?;
48 generate_assists_docs(Mode::Overwrite)?; 50 generate_assists_docs(Mode::Overwrite)?;
49 generate_feature_docs(Mode::Overwrite)?; 51 generate_feature_docs(Mode::Overwrite)?;
52 generate_diagnostic_docs(Mode::Overwrite)?;
50 Ok(()) 53 Ok(())
51 } 54 }
52} 55}
diff --git a/xtask/src/codegen/gen_assists_docs.rs b/xtask/src/codegen/gen_assists_docs.rs
index ff307e2aa..d7c85ebe9 100644
--- a/xtask/src/codegen/gen_assists_docs.rs
+++ b/xtask/src/codegen/gen_assists_docs.rs
@@ -1,6 +1,6 @@
1//! Generates `assists.md` documentation. 1//! Generates `assists.md` documentation.
2 2
3use std::{fmt, fs, path::Path}; 3use std::{fmt, path::Path};
4 4
5use crate::{ 5use crate::{
6 codegen::{self, extract_comment_blocks_with_empty_lines, reformat, Location, Mode, PREAMBLE}, 6 codegen::{self, extract_comment_blocks_with_empty_lines, reformat, Location, Mode, PREAMBLE},
@@ -39,7 +39,7 @@ impl Assist {
39 return Ok(res); 39 return Ok(res);
40 40
41 fn collect_file(acc: &mut Vec<Assist>, path: &Path) -> Result<()> { 41 fn collect_file(acc: &mut Vec<Assist>, path: &Path) -> Result<()> {
42 let text = fs::read_to_string(path)?; 42 let text = xshell::read_file(path)?;
43 let comment_blocks = extract_comment_blocks_with_empty_lines("Assist", &text); 43 let comment_blocks = extract_comment_blocks_with_empty_lines("Assist", &text);
44 44
45 for block in comment_blocks { 45 for block in comment_blocks {
diff --git a/xtask/src/codegen/gen_diagnostic_docs.rs b/xtask/src/codegen/gen_diagnostic_docs.rs
new file mode 100644
index 000000000..00aaea5b7
--- /dev/null
+++ b/xtask/src/codegen/gen_diagnostic_docs.rs
@@ -0,0 +1,74 @@
1//! Generates `assists.md` documentation.
2
3use std::{fmt, path::PathBuf};
4
5use crate::{
6 codegen::{self, extract_comment_blocks_with_empty_lines, Location, Mode, PREAMBLE},
7 project_root, rust_files, Result,
8};
9
10pub fn generate_diagnostic_docs(mode: Mode) -> Result<()> {
11 let diagnostics = Diagnostic::collect()?;
12 let contents =
13 diagnostics.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n");
14 let contents = format!("//{}\n{}\n", PREAMBLE, contents.trim());
15 let dst = project_root().join("docs/user/generated_diagnostic.adoc");
16 codegen::update(&dst, &contents, mode)?;
17 Ok(())
18}
19
20#[derive(Debug)]
21struct Diagnostic {
22 id: String,
23 location: Location,
24 doc: String,
25}
26
27impl Diagnostic {
28 fn collect() -> Result<Vec<Diagnostic>> {
29 let mut res = Vec::new();
30 for path in rust_files(&project_root()) {
31 collect_file(&mut res, path)?;
32 }
33 res.sort_by(|lhs, rhs| lhs.id.cmp(&rhs.id));
34 return Ok(res);
35
36 fn collect_file(acc: &mut Vec<Diagnostic>, path: PathBuf) -> Result<()> {
37 let text = xshell::read_file(&path)?;
38 let comment_blocks = extract_comment_blocks_with_empty_lines("Diagnostic", &text);
39
40 for block in comment_blocks {
41 let id = block.id;
42 if let Err(msg) = is_valid_diagnostic_name(&id) {
43 panic!("invalid diagnostic name: {:?}:\n {}", id, msg)
44 }
45 let doc = block.contents.join("\n");
46 let location = Location::new(path.clone(), block.line);
47 acc.push(Diagnostic { id, location, doc })
48 }
49
50 Ok(())
51 }
52 }
53}
54
55fn is_valid_diagnostic_name(diagnostic: &str) -> Result<(), String> {
56 let diagnostic = diagnostic.trim();
57 if diagnostic.find(char::is_whitespace).is_some() {
58 return Err("Diagnostic names can't contain whitespace symbols".into());
59 }
60 if diagnostic.chars().any(|c| c.is_ascii_uppercase()) {
61 return Err("Diagnostic names can't contain uppercase symbols".into());
62 }
63 if diagnostic.chars().any(|c| !c.is_ascii()) {
64 return Err("Diagnostic can't contain non-ASCII symbols".into());
65 }
66
67 Ok(())
68}
69
70impl fmt::Display for Diagnostic {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 writeln!(f, "=== {}\n**Source:** {}\n{}", self.id, self.location, self.doc)
73 }
74}
diff --git a/xtask/src/codegen/gen_feature_docs.rs b/xtask/src/codegen/gen_feature_docs.rs
index 341e67c73..065dd33f1 100644
--- a/xtask/src/codegen/gen_feature_docs.rs
+++ b/xtask/src/codegen/gen_feature_docs.rs
@@ -1,6 +1,6 @@
1//! Generates `assists.md` documentation. 1//! Generates `assists.md` documentation.
2 2
3use std::{fmt, fs, path::PathBuf}; 3use std::{fmt, path::PathBuf};
4 4
5use crate::{ 5use crate::{
6 codegen::{self, extract_comment_blocks_with_empty_lines, Location, Mode, PREAMBLE}, 6 codegen::{self, extract_comment_blocks_with_empty_lines, Location, Mode, PREAMBLE},
@@ -33,7 +33,7 @@ impl Feature {
33 return Ok(res); 33 return Ok(res);
34 34
35 fn collect_file(acc: &mut Vec<Feature>, path: PathBuf) -> Result<()> { 35 fn collect_file(acc: &mut Vec<Feature>, path: PathBuf) -> Result<()> {
36 let text = fs::read_to_string(&path)?; 36 let text = xshell::read_file(&path)?;
37 let comment_blocks = extract_comment_blocks_with_empty_lines("Feature", &text); 37 let comment_blocks = extract_comment_blocks_with_empty_lines("Feature", &text);
38 38
39 for block in comment_blocks { 39 for block in comment_blocks {
diff --git a/xtask/src/codegen/gen_lint_completions.rs b/xtask/src/codegen/gen_lint_completions.rs
new file mode 100644
index 000000000..cffe954f8
--- /dev/null
+++ b/xtask/src/codegen/gen_lint_completions.rs
@@ -0,0 +1,113 @@
1//! Generates descriptors structure for unstable feature from Unstable Book
2use std::path::{Path, PathBuf};
3
4use quote::quote;
5use walkdir::WalkDir;
6use xshell::{cmd, read_file};
7
8use crate::{
9 codegen::{project_root, reformat, update, Mode, Result},
10 run_rustfmt,
11};
12
13pub fn generate_lint_completions(mode: Mode) -> Result<()> {
14 if !Path::new("./target/rust").exists() {
15 cmd!("git clone --depth=1 https://github.com/rust-lang/rust ./target/rust").run()?;
16 }
17
18 let ts_features = generate_descriptor("./target/rust/src/doc/unstable-book/src".into())?;
19 cmd!("curl http://rust-lang.github.io/rust-clippy/master/lints.json --output ./target/clippy_lints.json").run()?;
20
21 let ts_clippy = generate_descriptor_clippy(&Path::new("./target/clippy_lints.json"))?;
22 let ts = quote! {
23 use crate::complete_attribute::LintCompletion;
24 #ts_features
25 #ts_clippy
26 };
27 let contents = reformat(ts.to_string().as_str())?;
28
29 let destination = project_root().join("crates/completion/src/generated_lint_completions.rs");
30 update(destination.as_path(), &contents, mode)?;
31 run_rustfmt(mode)?;
32
33 Ok(())
34}
35
36fn generate_descriptor(src_dir: PathBuf) -> Result<proc_macro2::TokenStream> {
37 let definitions = ["language-features", "library-features"]
38 .iter()
39 .flat_map(|it| WalkDir::new(src_dir.join(it)))
40 .filter_map(|e| e.ok())
41 .filter(|entry| {
42 // Get all `.md ` files
43 entry.file_type().is_file() && entry.path().extension().unwrap_or_default() == "md"
44 })
45 .map(|entry| {
46 let path = entry.path();
47 let feature_ident = path.file_stem().unwrap().to_str().unwrap().replace("-", "_");
48 let doc = read_file(path).unwrap();
49
50 quote! { LintCompletion { label: #feature_ident, description: #doc } }
51 });
52
53 let ts = quote! {
54 pub(super) const FEATURES: &[LintCompletion] = &[
55 #(#definitions),*
56 ];
57 };
58
59 Ok(ts)
60}
61
62#[derive(Default)]
63struct ClippyLint {
64 help: String,
65 id: String,
66}
67
68fn generate_descriptor_clippy(path: &Path) -> Result<proc_macro2::TokenStream> {
69 let file_content = read_file(path)?;
70 let mut clippy_lints: Vec<ClippyLint> = vec![];
71
72 for line in file_content.lines().map(|line| line.trim()) {
73 if line.starts_with(r#""id":"#) {
74 let clippy_lint = ClippyLint {
75 id: line
76 .strip_prefix(r#""id": ""#)
77 .expect("should be prefixed by id")
78 .strip_suffix(r#"","#)
79 .expect("should be suffixed by comma")
80 .into(),
81 help: String::new(),
82 };
83 clippy_lints.push(clippy_lint)
84 } else if line.starts_with(r#""What it does":"#) {
85 // Typical line to strip: "What is doest": "Here is my useful content",
86 let prefix_to_strip = r#""What it does": ""#;
87 let suffix_to_strip = r#"","#;
88
89 let clippy_lint = clippy_lints.last_mut().expect("clippy lint must already exist");
90 clippy_lint.help = line
91 .strip_prefix(prefix_to_strip)
92 .expect("should be prefixed by what it does")
93 .strip_suffix(suffix_to_strip)
94 .expect("should be suffixed by comma")
95 .into();
96 }
97 }
98
99 let definitions = clippy_lints.into_iter().map(|clippy_lint| {
100 let lint_ident = format!("clippy::{}", clippy_lint.id);
101 let doc = clippy_lint.help;
102
103 quote! { LintCompletion { label: #lint_ident, description: #doc } }
104 });
105
106 let ts = quote! {
107 pub(super) const CLIPPY_LINTS: &[LintCompletion] = &[
108 #(#definitions),*
109 ];
110 };
111
112 Ok(ts)
113}
diff --git a/xtask/src/codegen/gen_parser_tests.rs b/xtask/src/codegen/gen_parser_tests.rs
index 96fdd9216..19ae949d4 100644
--- a/xtask/src/codegen/gen_parser_tests.rs
+++ b/xtask/src/codegen/gen_parser_tests.rs
@@ -124,7 +124,7 @@ fn existing_tests(dir: &Path, ok: bool) -> Result<HashMap<String, (PathBuf, Test
124 let file_name = path.file_name().unwrap().to_str().unwrap(); 124 let file_name = path.file_name().unwrap().to_str().unwrap();
125 file_name[5..file_name.len() - 3].to_string() 125 file_name[5..file_name.len() - 3].to_string()
126 }; 126 };
127 let text = fs::read_to_string(&path)?; 127 let text = xshell::read_file(&path)?;
128 let test = Test { name: name.clone(), text, ok }; 128 let test = Test { name: name.clone(), text, ok };
129 if let Some(old) = res.insert(name, (path, test)) { 129 if let Some(old) = res.insert(name, (path, test)) {
130 println!("Duplicate test: {:?}", old); 130 println!("Duplicate test: {:?}", old);
diff --git a/xtask/src/install.rs b/xtask/src/install.rs
index 789e9f27b..78a8af797 100644
--- a/xtask/src/install.rs
+++ b/xtask/src/install.rs
@@ -13,8 +13,43 @@ pub struct InstallCmd {
13 pub server: Option<ServerOpt>, 13 pub server: Option<ServerOpt>,
14} 14}
15 15
16#[derive(Clone, Copy)]
16pub enum ClientOpt { 17pub enum ClientOpt {
17 VsCode, 18 VsCode,
19 VsCodeInsiders,
20 VsCodium,
21 VsCodeOss,
22 Any,
23}
24
25impl ClientOpt {
26 pub const fn as_cmds(&self) -> &'static [&'static str] {
27 match self {
28 ClientOpt::VsCode => &["code"],
29 ClientOpt::VsCodeInsiders => &["code-insiders"],
30 ClientOpt::VsCodium => &["codium"],
31 ClientOpt::VsCodeOss => &["code-oss"],
32 ClientOpt::Any => &["code", "code-insiders", "codium", "code-oss"],
33 }
34 }
35}
36
37impl Default for ClientOpt {
38 fn default() -> Self {
39 ClientOpt::Any
40 }
41}
42
43impl std::str::FromStr for ClientOpt {
44 type Err = anyhow::Error;
45
46 fn from_str(s: &str) -> Result<Self, Self::Err> {
47 [ClientOpt::VsCode, ClientOpt::VsCodeInsiders, ClientOpt::VsCodium, ClientOpt::VsCodeOss]
48 .iter()
49 .copied()
50 .find(|c| [s] == c.as_cmds())
51 .ok_or_else(|| anyhow::format_err!("no such client"))
52 }
18} 53}
19 54
20pub struct ServerOpt { 55pub struct ServerOpt {
@@ -74,17 +109,13 @@ fn fix_path_for_mac() -> Result<()> {
74 Ok(()) 109 Ok(())
75} 110}
76 111
77fn install_client(ClientOpt::VsCode: ClientOpt) -> Result<()> { 112fn install_client(client_opt: ClientOpt) -> Result<()> {
78 let _dir = pushd("./editors/code")?; 113 let _dir = pushd("./editors/code");
79 114
80 let find_code = |f: fn(&str) -> bool| -> Result<&'static str> { 115 let find_code = |f: fn(&str) -> bool| -> Result<&'static str> {
81 ["code", "code-insiders", "codium", "code-oss"] 116 client_opt.as_cmds().iter().copied().find(|bin| f(bin)).ok_or_else(|| {
82 .iter() 117 format_err!("Can't execute `code --version`. Perhaps it is not in $PATH?")
83 .copied() 118 })
84 .find(|bin| f(bin))
85 .ok_or_else(|| {
86 format_err!("Can't execute `code --version`. Perhaps it is not in $PATH?")
87 })
88 }; 119 };
89 120
90 let installed_extensions = if cfg!(unix) { 121 let installed_extensions = if cfg!(unix) {
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 97e5dcd4e..536a67047 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -16,7 +16,7 @@ use xshell::pushd;
16use xtask::{ 16use xtask::{
17 codegen::{self, Mode}, 17 codegen::{self, Mode},
18 dist::DistCmd, 18 dist::DistCmd,
19 install::{ClientOpt, InstallCmd, Malloc, ServerOpt}, 19 install::{InstallCmd, Malloc, ServerOpt},
20 metrics::MetricsCmd, 20 metrics::MetricsCmd,
21 pre_cache::PreCacheCmd, 21 pre_cache::PreCacheCmd,
22 pre_commit, project_root, 22 pre_commit, project_root,
@@ -46,19 +46,20 @@ USAGE:
46 cargo xtask install [FLAGS] 46 cargo xtask install [FLAGS]
47 47
48FLAGS: 48FLAGS:
49 --client-code Install only VS Code plugin 49 --client[=CLIENT] Install only VS Code plugin.
50 --server Install only the language server 50 CLIENT is one of 'code', 'code-insiders', 'codium', or 'code-oss'
51 --mimalloc Use mimalloc for server 51 --server Install only the language server
52 -h, --help Prints help information 52 --mimalloc Use mimalloc for server
53 -h, --help Prints help information
53 " 54 "
54 ); 55 );
55 return Ok(()); 56 return Ok(());
56 } 57 }
57 let server = args.contains("--server"); 58 let server = args.contains("--server");
58 let client_code = args.contains("--client-code"); 59 let client_code = args.contains("--client");
59 if server && client_code { 60 if server && client_code {
60 eprintln!( 61 eprintln!(
61 "error: The argument `--server` cannot be used with `--client-code`\n\n\ 62 "error: The argument `--server` cannot be used with `--client`\n\n\
62 For more information try --help" 63 For more information try --help"
63 ); 64 );
64 return Ok(()); 65 return Ok(());
@@ -67,10 +68,12 @@ FLAGS:
67 let malloc = 68 let malloc =
68 if args.contains("--mimalloc") { Malloc::Mimalloc } else { Malloc::System }; 69 if args.contains("--mimalloc") { Malloc::Mimalloc } else { Malloc::System };
69 70
71 let client_opt = args.opt_value_from_str("--client")?;
72
70 args.finish()?; 73 args.finish()?;
71 74
72 InstallCmd { 75 InstallCmd {
73 client: if server { None } else { Some(ClientOpt::VsCode) }, 76 client: if server { None } else { Some(client_opt.unwrap_or_default()) },
74 server: if client_code { None } else { Some(ServerOpt { malloc }) }, 77 server: if client_code { None } else { Some(ServerOpt { malloc }) },
75 } 78 }
76 .run() 79 .run()
diff --git a/xtask/src/release.rs b/xtask/src/release.rs
index 14fc1f0dd..3cf0d849f 100644
--- a/xtask/src/release.rs
+++ b/xtask/src/release.rs
@@ -52,7 +52,14 @@ https://github.com/sponsors/rust-analyzer[GitHub Sponsors].
52 let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n)); 52 let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n));
53 write_file(&path, &contents)?; 53 write_file(&path, &contents)?;
54 54
55 for &adoc in ["manual.adoc", "generated_features.adoc", "generated_assists.adoc"].iter() { 55 for &adoc in [
56 "manual.adoc",
57 "generated_features.adoc",
58 "generated_assists.adoc",
59 "generated_diagnostic.adoc",
60 ]
61 .iter()
62 {
56 let src = project_root().join("./docs/user/").join(adoc); 63 let src = project_root().join("./docs/user/").join(adoc);
57 let dst = website_root.join(adoc); 64 let dst = website_root.join(adoc);
58 cp(src, dst)?; 65 cp(src, dst)?;
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs
index 460069407..9de60c76c 100644
--- a/xtask/tests/tidy.rs
+++ b/xtask/tests/tidy.rs
@@ -42,6 +42,7 @@ fn smoke_test_docs_generation() {
42 // We don't commit docs to the repo, so we can just overwrite in tests. 42 // We don't commit docs to the repo, so we can just overwrite in tests.
43 codegen::generate_assists_docs(Mode::Overwrite).unwrap(); 43 codegen::generate_assists_docs(Mode::Overwrite).unwrap();
44 codegen::generate_feature_docs(Mode::Overwrite).unwrap(); 44 codegen::generate_feature_docs(Mode::Overwrite).unwrap();
45 codegen::generate_diagnostic_docs(Mode::Overwrite).unwrap();
45} 46}
46 47
47#[test] 48#[test]
@@ -130,6 +131,14 @@ https://github.blog/2015-06-08-how-to-undo-almost-anything-with-git/#redo-after-
130} 131}
131 132
132fn deny_clippy(path: &PathBuf, text: &String) { 133fn deny_clippy(path: &PathBuf, text: &String) {
134 let ignore = &[
135 // The documentation in string literals may contain anything for its own purposes
136 "completion/src/generated_lint_completions.rs",
137 ];
138 if ignore.iter().any(|p| path.ends_with(p)) {
139 return;
140 }
141
133 if text.contains("[\u{61}llow(clippy") { 142 if text.contains("[\u{61}llow(clippy") {
134 panic!( 143 panic!(
135 "\n\nallowing lints is forbidden: {}. 144 "\n\nallowing lints is forbidden: {}.
@@ -213,7 +222,7 @@ fn check_todo(path: &Path, text: &str) {
213 // `ast::make`. 222 // `ast::make`.
214 "ast/make.rs", 223 "ast/make.rs",
215 // The documentation in string literals may contain anything for its own purposes 224 // The documentation in string literals may contain anything for its own purposes
216 "completion/src/generated_features.rs", 225 "completion/src/generated_lint_completions.rs",
217 ]; 226 ];
218 if need_todo.iter().any(|p| path.ends_with(p)) { 227 if need_todo.iter().any(|p| path.ends_with(p)) {
219 return; 228 return;