Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-db/src/tests/sourcegen_lints.rs')
-rw-r--r--crates/ide-db/src/tests/sourcegen_lints.rs173
1 files changed, 115 insertions, 58 deletions
diff --git a/crates/ide-db/src/tests/sourcegen_lints.rs b/crates/ide-db/src/tests/sourcegen_lints.rs
index c7d5f3613d..c8cf87d3c2 100644
--- a/crates/ide-db/src/tests/sourcegen_lints.rs
+++ b/crates/ide-db/src/tests/sourcegen_lints.rs
@@ -1,4 +1,5 @@
-//! Generates descriptors structure for unstable feature from Unstable Book
+//! Generates descriptor structures for unstable features from the unstable book
+//! and lints from rustc, rustdoc, and clippy.
use std::{borrow::Cow, fs, path::Path};
use itertools::Itertools;
@@ -6,6 +7,8 @@ use stdx::format_to;
use test_utils::project_root;
use xshell::{cmd, Shell};
+const DESTINATION: &str = "crates/ide-db/src/generated/lints.rs";
+
/// This clones rustc repo, and so is not worth to keep up-to-date. We update
/// manually by un-ignoring the test from time to time.
#[test]
@@ -14,11 +17,21 @@ fn sourcegen_lint_completions() {
let sh = &Shell::new().unwrap();
let rust_repo = project_root().join("./target/rust");
- if !rust_repo.exists() {
+ if rust_repo.exists() {
+ cmd!(sh, "git -C {rust_repo} pull --rebase").run().unwrap();
+ } else {
cmd!(sh, "git clone --depth=1 https://github.com/rust-lang/rust {rust_repo}")
.run()
.unwrap();
}
+ // need submodules for Cargo to parse the workspace correctly
+ cmd!(
+ sh,
+ "git -C {rust_repo} submodule update --init --recursive --depth=1 --
+ compiler library src/tools"
+ )
+ .run()
+ .unwrap();
let mut contents = String::from(
r"
@@ -27,17 +40,28 @@ pub struct Lint {
pub label: &'static str,
pub description: &'static str,
}
+
pub struct LintGroup {
pub lint: Lint,
pub children: &'static [&'static str],
}
+
",
);
generate_lint_descriptor(sh, &mut contents);
contents.push('\n');
- generate_feature_descriptor(&mut contents, &rust_repo.join("src/doc/unstable-book/src"));
+ let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
+ let unstable_book = project_root().join("./target/unstable-book-gen");
+ cmd!(
+ sh,
+ "{cargo} run --manifest-path {rust_repo}/src/tools/unstable-book-gen/Cargo.toml --
+ {rust_repo}/library {rust_repo}/compiler {rust_repo}/src {unstable_book}"
+ )
+ .run()
+ .unwrap();
+ generate_feature_descriptor(&mut contents, &unstable_book.join("src"));
contents.push('\n');
let lints_json = project_root().join("./target/clippy_lints.json");
@@ -51,41 +75,60 @@ pub struct LintGroup {
let contents = sourcegen::add_preamble("sourcegen_lints", sourcegen::reformat(contents));
- let destination = project_root().join("crates/ide_db/src/generated/lints.rs");
+ let destination = project_root().join(DESTINATION);
sourcegen::ensure_file_contents(destination.as_path(), &contents);
}
+/// Parses the output of `rustdoc -Whelp` and prints `Lint` and `LintGroup` constants into `buf`.
+///
+/// As of writing, the output of `rustc -Whelp` (not rustdoc) has the following format:
+///
+/// ```text
+/// Lint checks provided by rustc:
+///
+/// name default meaning
+/// ---- ------- -------
+///
+/// ...
+///
+/// Lint groups provided by rustc:
+///
+/// name sub-lints
+/// ---- ---------
+///
+/// ...
+/// ```
+///
+/// `rustdoc -Whelp` (and any other custom `rustc` driver) adds another two
+/// tables after the `rustc` ones, with a different title but the same format.
fn generate_lint_descriptor(sh: &Shell, buf: &mut String) {
- // FIXME: rustdoc currently requires an input file for -Whelp cc https://github.com/rust-lang/rust/pull/88831
- let file = project_root().join(file!());
- let stdout = cmd!(sh, "rustdoc -W help {file}").read().unwrap();
- let start_lints = stdout.find("---- ------- -------").unwrap();
- let start_lint_groups = stdout.find("---- ---------").unwrap();
- let start_lints_rustdoc =
- stdout.find("Lint checks provided by plugins loaded by this crate:").unwrap();
- let start_lint_groups_rustdoc =
- stdout.find("Lint groups provided by plugins loaded by this crate:").unwrap();
+ let stdout = cmd!(sh, "rustdoc -Whelp").read().unwrap();
+ let lints_pat = "---- ------- -------\n";
+ let lint_groups_pat = "---- ---------\n";
+ let lints = find_and_slice(&stdout, lints_pat);
+ let lint_groups = find_and_slice(lints, lint_groups_pat);
+ let lints_rustdoc = find_and_slice(lint_groups, lints_pat);
+ let lint_groups_rustdoc = find_and_slice(lints_rustdoc, lint_groups_pat);
buf.push_str(r#"pub const DEFAULT_LINTS: &[Lint] = &["#);
buf.push('\n');
- let lints = stdout[start_lints..].lines().skip(1).take_while(|l| !l.is_empty()).map(|line| {
+ let lints = lints.lines().take_while(|l| !l.is_empty()).map(|line| {
let (name, rest) = line.trim().split_once(char::is_whitespace).unwrap();
let (_default_level, description) = rest.trim().split_once(char::is_whitespace).unwrap();
(name.trim(), Cow::Borrowed(description.trim()), vec![])
});
- let lint_groups =
- stdout[start_lint_groups..].lines().skip(1).take_while(|l| !l.is_empty()).map(|line| {
- let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap();
- (
- name.trim(),
- format!("lint group for: {}", lints.trim()).into(),
- lints
- .split_ascii_whitespace()
- .map(|s| s.trim().trim_matches(',').replace('-', "_"))
- .collect(),
- )
- });
+ let lint_groups = lint_groups.lines().take_while(|l| !l.is_empty()).map(|line| {
+ let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap();
+ (
+ name.trim(),
+ format!("lint group for: {}", lints.trim()).into(),
+ lints
+ .split_ascii_whitespace()
+ .map(|s| s.trim().trim_matches(',').replace('-', "_"))
+ .collect(),
+ )
+ });
let lints = lints
.chain(lint_groups)
@@ -94,7 +137,8 @@ fn generate_lint_descriptor(sh: &Shell, buf: &mut String) {
for (name, description, ..) in &lints {
push_lint_completion(buf, &name.replace('-', "_"), description);
}
- buf.push_str("];\n");
+ buf.push_str("];\n\n");
+
buf.push_str(r#"pub const DEFAULT_LINT_GROUPS: &[LintGroup] = &["#);
for (name, description, children) in &lints {
if !children.is_empty() {
@@ -115,27 +159,23 @@ fn generate_lint_descriptor(sh: &Shell, buf: &mut String) {
buf.push_str(r#"pub const RUSTDOC_LINTS: &[Lint] = &["#);
buf.push('\n');
- let lints_rustdoc =
- stdout[start_lints_rustdoc..].lines().skip(2).take_while(|l| !l.is_empty()).map(|line| {
- let (name, rest) = line.trim().split_once(char::is_whitespace).unwrap();
- let (_default_level, description) =
- rest.trim().split_once(char::is_whitespace).unwrap();
- (name.trim(), Cow::Borrowed(description.trim()), vec![])
- });
+ let lints_rustdoc = lints_rustdoc.lines().take_while(|l| !l.is_empty()).map(|line| {
+ let (name, rest) = line.trim().split_once(char::is_whitespace).unwrap();
+ let (_default_level, description) = rest.trim().split_once(char::is_whitespace).unwrap();
+ (name.trim(), Cow::Borrowed(description.trim()), vec![])
+ });
let lint_groups_rustdoc =
- stdout[start_lint_groups_rustdoc..].lines().skip(2).take_while(|l| !l.is_empty()).map(
- |line| {
- let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap();
- (
- name.trim(),
- format!("lint group for: {}", lints.trim()).into(),
- lints
- .split_ascii_whitespace()
- .map(|s| s.trim().trim_matches(',').replace('-', "_"))
- .collect(),
- )
- },
- );
+ lint_groups_rustdoc.lines().take_while(|l| !l.is_empty()).map(|line| {
+ let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap();
+ (
+ name.trim(),
+ format!("lint group for: {}", lints.trim()).into(),
+ lints
+ .split_ascii_whitespace()
+ .map(|s| s.trim().trim_matches(',').replace('-', "_"))
+ .collect(),
+ )
+ });
let lints_rustdoc = lints_rustdoc
.chain(lint_groups_rustdoc)
@@ -145,7 +185,7 @@ fn generate_lint_descriptor(sh: &Shell, buf: &mut String) {
for (name, description, ..) in &lints_rustdoc {
push_lint_completion(buf, &name.replace('-', "_"), description)
}
- buf.push_str("];\n");
+ buf.push_str("];\n\n");
buf.push_str(r#"pub const RUSTDOC_LINT_GROUPS: &[LintGroup] = &["#);
for (name, description, children) in &lints_rustdoc {
@@ -157,14 +197,24 @@ fn generate_lint_descriptor(sh: &Shell, buf: &mut String) {
buf.push_str("];\n");
}
+#[track_caller]
+fn find_and_slice<'a>(i: &'a str, p: &str) -> &'a str {
+ let idx = i.find(p).unwrap();
+ &i[idx + p.len()..]
+}
+
+/// Parses the unstable book `src_dir` and prints a constant with the list of
+/// unstable features into `buf`.
+///
+/// It does this by looking for all `.md` files in the `language-features` and
+/// `library-features` directories, and using the file name as the feature
+/// name, and the file contents as the feature description.
fn generate_feature_descriptor(buf: &mut String, src_dir: &Path) {
let mut features = ["language-features", "library-features"]
.into_iter()
.flat_map(|it| sourcegen::list_files(&src_dir.join(it)))
- .filter(|path| {
- // Get all `.md ` files
- path.extension().unwrap_or_default().to_str().unwrap_or_default() == "md"
- })
+ // Get all `.md` files
+ .filter(|path| path.extension() == Some("md".as_ref()))
.map(|path| {
let feature_ident = path.file_stem().unwrap().to_str().unwrap().replace('-', "_");
let doc = fs::read_to_string(path).unwrap();
@@ -196,7 +246,7 @@ fn generate_descriptor_clippy(buf: &mut String, path: &Path) {
let mut clippy_lints: Vec<ClippyLint> = Vec::new();
let mut clippy_groups: std::collections::BTreeMap<String, Vec<String>> = Default::default();
- for line in file_content.lines().map(|line| line.trim()) {
+ for line in file_content.lines().map(str::trim) {
if let Some(line) = line.strip_prefix(r#""id": ""#) {
let clippy_lint = ClippyLint {
id: line.strip_suffix(r#"","#).expect("should be suffixed by comma").into(),
@@ -211,12 +261,19 @@ fn generate_descriptor_clippy(buf: &mut String, path: &Path) {
.push(clippy_lints.last().unwrap().id.clone());
}
} else if let Some(line) = line.strip_prefix(r#""docs": ""#) {
- let prefix_to_strip = r#" ### What it does"#;
- let line = match line.strip_prefix(prefix_to_strip) {
- Some(line) => line,
+ let header = "### What it does";
+ let line = match line.find(header) {
+ Some(idx) => &line[idx + header.len()..],
None => {
- eprintln!("unexpected clippy prefix for {}", clippy_lints.last().unwrap().id);
- continue;
+ let id = &clippy_lints.last().unwrap().id;
+ // these just don't have the common header
+ let allowed = ["allow_attributes", "read_line_without_trim"];
+ if allowed.contains(&id.as_str()) {
+ line
+ } else {
+ eprintln!("\nunexpected clippy prefix for {id}, line={line:?}\n",);
+ continue;
+ }
}
};
// Only take the description, any more than this is a lot of additional data we would embed into the exe